Swift conditional data printing on UITableView - ios

I have an UITableView, this is its cellForRowAtIndexPath and its numberOfRowsInSection:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("customTableViewCell") as! UITableViewCell
let task = frc.objectAtIndexPath(indexPath) as! Task
cell.textLabel?.text = task.summary
var detail = task.detail
var context = task.context
var due = task.date
var status = task.status
var responsible = task.responsable
var folder = task.folder
cell.detailTextLabel?.text = "Contexte: \(context), Detail: \(detail), Status: \(status), Ending date: \(due)"
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let numberOfRowsInSection = frc.sections?[section].numberOfObjects
return numberOfRowsInSection!
}
What I'm trying to do is , when I click on a line, it opens a detail view of the line, so I try to pass datas with a prepareForSegue but I only succeed to send on the segue datas from my database and not datas from the selected line, like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier{
//On vérifie que l'identifier est le bon, permet d'envoyer qu'à la View qu'on veut si a le risque d'envoyer à plusieurs. Si on veut envoyer ailleurs, il suffit de créer la vue en question et de rajouter un "case" avec le nom du nouvel identifier.
switch identifier {
case "Show Detail":
//Creation du lien vers la base SQLite
let entityDescription = NSEntityDescription.entityForName("Task", inManagedObjectContext: self.context!)
let request = NSFetchRequest()
request.entity = entityDescription
var cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("customTableViewCell") as! UITableViewCell
var error: NSError?
var objects = self.context?.executeFetchRequest(request, error: &error)
let match = objects![0] as! NSManagedObject
let editTaskVC = segue.destinationViewController as! EditTaskViewController
if let indexPath = self.tableView.indexPathForCell(sender as! UITableViewCell){
editTaskVC.Name = match.valueForKey("summary") as! String
editTaskVC.Detail = match.valueForKey("detail") as! String
editTaskVC.Status = match.valueForKey("status") as! String
editTaskVC.Owner = match.valueForKey("responsable") as! String
editTaskVC.Context = match.valueForKey("context") as! String
editTaskVC.IdValue = match.valueForKey("id") as? String
editTaskVC.Field = match.valueForKey("folder") as! String
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd hh:mm:ss"
let date = dateFormatter.dateFromString(match.valueForKey("date") as! String)
editTaskVC.EndDatePicker?.date = date!
}
default: break
}
}
}
What I try to do, is sending to the destinationViewController datas from the just clicked row and not the database, something like this:
editTaskVC.Name = cell.textLabel?.text
I have searched on the net and saw some solutions, like using the didSelectRowAtIndexPath but without success.

You have a number of problems with your approach.
For starters you should not be calling dequeueReusableCellWithIdentifier outside of your cellForRowAtIndexPath method.
Next, you basic approach is wrong.
View objects do not store state, they display it and collect input from the user. A table view cell can be scrolled off the screen at any time and it's current settings will be lost. When the user makes changes to the values of a cell you need to save that to a model object that stores the current state of you data.
Normally this means saving changes to your database if that's what you're using as a model. If you want the changes to stay pending until the user clicks a save button, or for some other reason are not ready to save the changes to your database then you need to save them to some data model specific to the table view. An array managed by the view controller works well for this.
In any case, when the user taps a cell, you should be looking in the data for your table view for the data to pass on to the detail controller, not trying to read it from the cell.
What you want to do is to save the indexPath of the tapped cell to an instance variable. Then in prepareForSegue, if you figure out that this is a segue triggered by the user tapping a cell, look at that indexPath and fetch the appropriate data for that indexPath to pass on to the next view controller.

I think that you destination view controller is not affected because you do:
if let indexPath = self.tableView.indexPathForCell(sender as! UITableViewCell){
//code
}
This condition is always false.
Remove this condition and normally it should work.
NB: see Duncan C post.

Related

Unable to reload tableview properly swift ios

I have a tableview which is part of a navigation controller that returns a list of questions.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for:indexPath) as! AnswerTableViewCell
let data = quesList[indexPath.row]
cell.labelName.text = data.sender_name;
cell.labelQuestion.text = data.text;
return cell
}
func fetchQues(){
Ref.queryOrdered(byChild: currentuser).queryEqual(toValue: nil).observe(DataEventType.value, with:{(snapshot)in
if snapshot.childrenCount>0{
self.quesList.removeAll()
for data in snapshot.children.allObjects as![DataSnapshot]{
let dataObject = data.value as? [String: AnyObject]
let userid = dataObject?["sender_id"];
let username = dataObject?["sender_name"];
let questext = dataObject?["text"];
let questionid = dataObject?["id"];
let data = Question(id: questionid as! String, sender_id: userid as! String, sender_name: username as! String, text: questext as! String)
self.quesList.insert(data, at :0)
}
self.AnswerTable.reloadData()
}
})
}
I then have a didselectrow function to segue to a VC part of this navigation stack that lets the user input their answer for the question.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let data = quesList[indexPath.row]
questionselect1 = data.text;
questionid1 = data.id;
performSegue(withIdentifier: self.questSegue, sender: nil)
}
After the user submit the answer to the question, it adds a dictionary into the question dictionary and returns to the list of question screen. The queryorder(bychild:currentuser).queryeuqual(tovalue:nil) would only return questions that have not been answered by the current user. So everytime a user answers a question, that question would get removed from the list.
#IBAction func submitAnswer(_ sender: Any) {
if questiontext.text != "" {
DBProvider.Instance.postAnswers(senderID: userid, senderName: username2, text: questiontext.text!)
DBProvider.Instance.postAnswered()
self.questiontext.text = "";
self.navigationController!.popViewController(animated: true)
//append data to the current question
} else {
alertTheUser(title: "Please Submit A Valid Answer", message: "Question Field Cannot Be Empty");
}
}
This works fine at the moment, however, when there is only one question on the list, and the user answers it, and returns to the questions tableview, the last question does not get removed from the list, which it should be since it has been answered.
I just have to add that when I log out of my app completely and login again, the questionlist is empty and is refreshed properly, since all the question have been answered.
I have been trying to figure out why this is happening by changing the segueing method and reloading the view with viewwillload or viewdidload and it does not work.
Hopefully someone understands what I am saying and can provide a answer to my bug.
Without looking at your code it's hard to say exactly but this could be caused by not having a background colour set on the root UIView of one of your View Controllers.

View Parse PFFile PDF in WebView using Xcode7 and Swift 2

I have an iOS project I'm working on using Xcode7 and Swift2. I have a PDF that is saving to Parse. It is saving to a Parse Class called IncomingRecipe. In this Class is a FileName column with type as a String. It also has a column called PDFData and is type PFFile. I want it so when the user clicks on a TableViewCell it segues to a new View Controller and displays the PDF in a WebView.
Currently these fileNames are in a TableView. I have a segue that goes to the View Controller with the WebView. It passes along the name of the fileName from the TableViewCell as a Global variable.
My query for the data for the TableView code for parse is:
var fileName = [String]()
var PDFData = [PFFile]()
var getRecipeQuery = PFQuery(className: "IncomingRecipe")
// Match the query with only items that the current user uploaded
getRecipeQuery.whereKey("userId", equalTo: appUserId)
getRecipeQuery.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
// Check to see if 'objects' exist
if let objects = objects {
for object in objects {
// An AnyObject that needs to be cast as a String
var recipeName = object["fileName"] as! String
self.fileName.append(object["fileName"] as! String)
self.objectid.append(object["objectid"] as! String)
self.userId.append(object["userId"] as! String)
self.PDFData.append(object["PDFData"] as! PFFile)
self.myFilesTable.reloadData()
}
}
}
The TableView loads the fileName as:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "PDFTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! PDFTableViewCell
cell.textLabel?.text = fileName[indexPath.row]
return cell
}
I have the code for passing the selected cell fileName to a Global variable as:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ItemView" {
let savedRecipedView = segue.destinationViewController as! PDFItemViewController
if let selectedRecipeCell = sender as? PDFTableViewCell {
let indexPath = myFilesTable.indexPathForCell(selectedRecipeCell)!
viewingPDFRecipe = fileName[indexPath.row]
print("Sending Click: \(viewingPDFRecipe)")
}
}
}
How can I get the PFFile of the PDA and display it in the WebView on the other View Controller? I can't figure out how to get all of this into a URL to be used with the WebView. I looked here and tried to implement this with mine, with no success. Thank you.
Don't just save the file name with
self.fileName.append(object["fileName"] as! String)
save the whole list of PFObjects. These objects will contain the file name (that you can use for the table view) and the PFFile reference (that you can use on drill down). Also, you don't appear to, but you shouldn't pass by global. It just looks like you're passing the file name in the segue.
Instead of passing the file name you should pass the whole PFObject. Then, in the destination view controller you can extract the PFFile reference and the URL it contains.

Swift: app crashes after deleting a column in Parse

In Parse I accidentally deleted a column called "likes" that counts the number of a likes a user receives for their blog post. I created the column again with the same name but now when I run my app it crashes and I receive this message "unexpectedly found nil while unwrapping an Optional value". It points to my code where its suppose to receive the "likes" in my cellForRowAtIndexPath. I pasted my code below. Is there any way I could fix this issue and stop it from crashing?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath?) -> PFTableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("BCell", forIndexPath: indexPath!) as! BlogCell
if let object : PFObject = self.blogPosts.objectAtIndex(indexPath!.row) as? PFObject {
cell.author.text = object["blogger"] as? String
cell.post.text = object["blogPost"] as? String
let dateUpdated = object.createdAt! as NSDate
let dateFormat = NSDateFormatter()
dateFormat.dateFormat = "h:mm a"
cell.timer.text = NSString(format: "%#", dateFormat.stringFromDate(dateUpdated)) as String
let like = object[("likes")] as! Int
cell.likeTracker.text = "\(like)"
}
return cell
}
I would inspect what's going on with the object if I were you. You clearly aren't getting data that you expected to be there. As a stopgap, you can change let like = object["likes"] as! Int to
if let like = object["likes"] as? Int {
cell.likeTracker.text = "\(like)"
}
If you do that, you will also want to implement the prepareForReuse method in BlogCell to set that label's text to nil or else you might have some weird cell reuse bugs.
Where you delete a column from uitableview , you need to delete data from data source, and update the delete index or reload the whole table .
Look for if you are missing that step

Swift and Parse - Message app, cannot display username within Xcode 6.3

I have some issue to display the 'author's username of each posts in my TableViewController.
It actually display the current user's username for all display posts, how to display each poster's username ?
I'm using Xcode 6.3 and Parse.com API.
The timeLabel is displayed correctly, but the userLabel display the current user who is logged in instead of the author of the post.
If I logged out and login with a different username all the userLabel change to the new user. The debug console display Optional("theNameOfTheCurrentUser") as many times as there are posts displayed.
Parse host 2 DB one for users (User) and one for posts (Poemes), there is a pointer in Poemes table to the specific user.
I update to Xcode 6.3 lately and had an error on var findLover:PFQuery = PFUser.query()
Value of optional type 'PFQuery?' not unwrapped
I add the exclamation mark (!) at the end of this line, which remove the error, is this causing the issue ?
I read Parse documentation and follow some exemples but looks like I'm a bit lost here, any help and suggestions will be highly appreciated, thanks.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:DisplayTableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! DisplayTableViewCell
let poeme:PFObject = self.timelineData.objectAtIndex(indexPath.row) as! PFObject
cell.poemeLabel.text = poeme.objectForKey("content") as? String
var dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd HH:mm"
cell.timeLabel.text = dateFormatter.stringFromDate(poeme.createdAt!)
var findUser:PFQuery = PFUser.query()!
findUser.findObjectsInBackgroundWithBlock {
(objects, error)->Void in
if var objects = objects {
let author:PFUser = (objects as NSArray).lastObject as! PFUser
cell.userLabel.text = author.username
println(author.username)
})
}
return cell
}
The function findUser.findObjectsInBackgroundWithBlock happens in the background, while the main thread still running, so by the time you get the response from parse with the values you need the cell you are trying to return in the function is long gone.
The easiest way to fix it is to fetch all the data you need before hand and safe it in a array and use this array to populate the cell.
Finally get it work for Xcode 6.3.2 changes, here is the result :
unwrap and optional seams to be my main problem :
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell {
let cell:DisplayTableViewCell = tableView!.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath!) as! DisplayTableViewCell
let poeme:PFObject = self.timelineData.objectAtIndex(indexPath!.row) as! PFObject
cell.poemeLabel.text = poeme.objectForKey("content") as! String
var dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd HH:mm"
cell.timeLabel.text = dateFormatter.stringFromDate(poeme.createdAt!)
var findUser:PFQuery = PFUser.query()!
findUser.whereKey("objectId", equalTo: poeme.objectForKey("user")!.objectId!!)
findLover.findObjectsInBackgroundWithBlock {
(objects, error)->Void in
if var objects = objects {
let author:PFUser = (objects as NSArray).lastObject as! PFUser
cell.userLabel.text = author.username
println(author.username)
})
}
}
return cell
}

Core Data to create TableViewCells Swift

So I am trying to create a TableViewCell with Core Data, but when defining the cells, they all turn in the last input at the Core Data. The app is taking the user textfield input and turning into the table view cell label, and the zipInStr to the TableViewCell detail.
This is the function that add the values to the CoreData:
#IBAction func createFav(sender: AnyObject) {
//Defining variables to save at core data
var newTextInput = textInput.text
var trimmNewTextInput = newTextInput.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
var zipInStr: String = zipCode.text!
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var newFav = NSEntityDescription.insertNewObjectForEntityForName("Cells", inManagedObjectContext: context) as NSManagedObject
newFav.setValue(zipInStr, forKey: "favsDictKey")
newFav.setValue(trimmNewTextInput, forKey: "favsDictValues")
context.save(nil)
println(newFav)
textInput.text = String()
}
}
And this is the function that is creating the TableViewCells:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Cells")
request.returnsObjectsAsFaults = false
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
var results: NSArray = context.executeFetchRequest(request, error: nil)!
println(results)
if (results.count == 0){
println("Error")
} else {
for res in results{
cell.textLabel?.text = res.valueForKey("favsDictValues") as? String
cell.detailTextLabel?.text = res.valueForKey("favsDictKey") as? String
}
}
return cell
}
I am pretty sure the error have something to do with the loop, since when I print the results I can see all the inputs with its respective values
Your setup is not correct. You should employ a NSFetchedResultsController (see Xcode template for easy code to copy) - this is the best way to populate a table view with Core Data.
In cellForRowAtIndexPath you simply retrieve the correct item for the index path. Don't put fetch request into this method, as it is potentially called many times (e.g. while scrolling).
let cell = self.fetchedResultsController.objectAtIndexPath(indexPath) as Cells
BTW, "Cells" seems to be an inappropriate name for an entity. You need a singular that describes the data you are displaying. Perhaps, "Favorite"?
As per my comment, to get your current setup working, make results into a stored property. Add
var results : NSArray = NSArray()
at the beginning of your class definition. Then move the code to populate the array from your cellForRowAtIndexPath to viewDidLoad:
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Cells")
request.returnsObjectsAsFaults = false
results = context.executeFetchRequest(request, error: nil)!
println("\(results)")
Now, the tableView will call cellForRowAtIndexPath multiple times, once for each row that it wants to display. So the code in that method needs to configure it with the data for the relevant row. The indexPath provided as an argument to the call indicates which row (and section, if you break your table view into sections) the tableView is requesting. So your code does not need to loop through all the values in results - it just needs to reference the correct element of the array.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let res = results[indexPath.row] as NSManagedObject
cell.textLabel?.text = res.valueForKey("favsDictValues") as? String
cell.detailTextLabel?.text = res.valueForKey("favsDictKey") as? String
return cell
}
That should get your tableView working (unless I've messed up some optionals or casts - if so, sorry, I'm still getting up to speed with Swift).
This approach will work fine provided your data is relatively static (i.e. doesn't change whilst the tableView is being displayed) and provided you don't want to break the table into sections. In either of these situations (and, some would say, in any case), you should use an NSFetchedResultsController. That will automatically keep track of how many (and which) items appear in each section, etc, and can also be configured to automatically update the tableView if new items are added to the data whilst it is being displayed.

Resources