Duplicate values appended in PFQueryTableViewController - ios

so I'm creating an App with Swift 2 and Xcode 7 and using Parse as my backend service.
I have two view controllers, a PFQueryTableViewController to show a list of PFObjects and the other the show the detail of selected cells.
I figured the way to do that is to append a unique object id to an array, and then to use didSelectRowAtIndexPath to perform segue.
But I'm having problems with append elements to the array here. Overtime I append and print the array, it shows elements 2 times. So if the correct array is [1,2,3,4], then what i'm getting is [1,2,3,4,1,2,3,4], really weird.
var arrayOfGameId = [String]()
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cellIdentifier = "cell"
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? PFTableViewCell
if cell == nil {
cell = PFTableViewCell(style: .Subtitle, reuseIdentifier: cellIdentifier)
}
if let object = object {
cell!.textLabel?.text = object["Title"] as? String
cell!.detailTextLabel?.text = object["Platform"] as? String
if let thumbnail = object["Image"]! as? PFFile {
cell!.imageView!.image = UIImage(named: "game1.png")
cell!.imageView!.file = thumbnail
}
let gameid = object["GameId"] as! String!
arrayOfGameId.append(gameid)
}
print(arrayOfGameId)
return cell
}

Since you're using a PFQueryTableViewController there is no need to make your own list of objectIds.
The returned PFObjects from queryForTable are automatically stored in a list called objects.
If you need to get the selected object, and segue to a detail view controller, you actually don't even need to use didSelectRowAtIndexPath, try the following.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using [segue destinationViewController]
var detailsVC = segue.destinationViewController as! DetailsViewController
// Pass the selected object to the destination view controller
if let indexPath = self.tableView.indexPathForSelectedRow() {
let row = Int(indexPath.row)
// selectedObject is the PFObject to be displayed
detailsVC.selectedObject = (objects?[row] as! PFObject)
}
}

Related

PrepareForSegue with UITableViewController - Pass data

I am having a huge problem with my code. My question is, how can I get the data from the tableviewcell the user clicks on to a detail viewcontroller.
I am already getting the data out of an array and displaying it in my tableviewcontroller, but how can I pass that data through to a new viewcontroller using prepareForSegue?
This is my code for displaying data in the cells.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "MealTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MealTableViewCell
// Fetches the appropriate meal for the data source layout.
let meal = meals[indexPath.row]
cell.nameLabel.text = meal.name
cell.photoImageView.image = meal.photo
cell.descriptionLabel.text = meal.description
cell.servedWithLabel.text = meal.servedWith
return cell
}
The problem is, that calling a prepareForSegue outside of that means that I cannot use for instance meal.name to pass the name through to the detail viewcontroller? What to do?
Hope you guys can help me - I have been struggling with this for some time now :-(
As pointed out by Arun, you need to use -prepareForSegue. Here's what I'd do to pass something to your detail view controller (say meal.name)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Use this to determine the selected index path. You can also store the selected index path in a variable using -didSelectRowAtIndexPath
let cell = sender as! UITableViewCell
let indexPath = self.tableView.indexPathForCell(cell)
// Get the relevant detail
let meal = meals[indexPath.row]
// Create reference and pass it
let detailViewController = segue.destinationViewController as! MyDetailViewController
detailViewController.mealName = meal.name
}
And eventually, just hook up the UITableViewCell with MyDetailViewController using your Storyboard (and select whatever presentation and transition style you prefer)
Edited for Swift 4
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let cell = sender as! UITableViewCell
let indexPath = self.tableView.indexPath(for: cell)
// Get the relevant detail
let meal = meals[indexPath!.row]
// Create reference and pass it
let detailViewController = segue.destination as! MyDetailViewController
detailViewController.mealName = meal.name
}
When you click a cell. It will call didSelectRow. In didSelectRow save the indexPath.row and do performseguewithidentifier
Now in prepareForSegue get the object from the array using meals[selectedIndex] and assign to detailViewController Objects.
Make sure segue is from ViewController to ViewController.

TableView showing wrong data after segue

This is a project is a simple car's dictionary, I am using core data, from a .csv file uploaded from a server.
When I select the word in the first tableview trigger a second page to read the definition in another tableview, there is the problem is always showing incorrect word and definition.
You are ignoring the section number in the index path you get from tableView.indexPathForSelectedRow. For a sectioned table, you need to translate a section/row combination into a data reference.
A standard way of doing that is with an array of arrays (e.g. dictionaryItems:[[Dictionary]]). That way, you can get an array of items by using the index path section on the outer array and the specific item by using the index path row on the array the section reference returns.
--- UPDATE with methods that need code changes in DictionaryTableViewController
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Assume a single section after a search
return (searchController.active) ? 1 : sectionTitles.count
}
// Create a standard way to get a Dictionary from an index path
func itemForIndexPath(indexPath: NSIndexPath) -> Dictionary? {
var result: Dictionary? = nil
if searchController.active {
result = searchResults[indexPath.row]
} else {
let wordKey = sectionTitles[indexPath.section]
if let items = cockpitDict[wordKey] {
result = items[indexPath.row]
}
}
return result
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! DictionaryTableViewCell
if let dictionary = itemForIndexPath(indexPath) {
cell.wordLabel.text = dictionary.word
cell.definitionSmallLabel.text = dictionary.definition
} else {
print("Cell error with path \(indexPath)")
}
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDictionaryDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let destinationController = segue.destinationViewController as! DictionaryDetailViewController
if let dictionary = itemForIndexPath(indexPath) {
destinationController.dictionary = dictionary
} else {
print("Segue error with path \(indexPath)")
}
searchController.active = false
}
}
}
I checked your code and think the trouble is with destinationController.dictionary = (searchController.active) ? searchResults[indexPath.row] : dictionaryItems[indexPath.row]
you should get dictionary like this (as you did in cellForRowAtIndexPath):
let dictionary = (searchController.active) ? searchResults[indexPath.row]: dictionaryItems[indexPath.row]
let wordKey = sectionTitles[indexPath.section]
let items = cockpitDict[wordKey]
Now item will be the dictionary to pass to detail view.
I got this idea, when I saw you are populating your data in table view very efficiently.

Table view Help…..selected cells from one table view passed to another table view swift

This is a two part question...
My problem is....figuring out how to have the selected cells on the first table view...(which are pfobjects from parse) save into an array and then be able to populate those selected cells on another tableview controller....
How can you store the selected cells of the first table view into ONE table view cell on the other tableview and then have a disclosure indicator on it that opens up to see all the added cells? sorry this is more conceptual than concrete code to change or add to... here is what i have so far...
thanks in advance friends.
override func tableView(tableView:UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell") as! PFTableViewCell!
if cell == nil {
cell = PFTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cellIdentifier")
}
// Extract values from the PFObject to display in the table cell
if let customerFirstName = object?["customerName"] as? String {
cell?.textLabel?.text = customerFirstName
}
if let customerStreetAddress = object?["customerStreetAddress"] as? String {
cell?.detailTextLabel?.text = customerStreetAddress
}
if let indexPaths = tableView.indexPathsForSelectedRows() {
for var i = 0; i < indexPaths.count; ++i {
var thisPath = (indexPaths as! [NSIndexPath])[i]
var cell = tableView.cellForRowAtIndexPath(thisPath)
if let cell = cell {
self.addedPoolarray.append(cell)
// Do something with the cell
// If it's a custom cell, downcast to the proper type
}
}
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toRoutesVc" {
let destination = segue.destinationViewController as! UITableViewController
}
}
Remove these code:
if let indexPaths = tableView.indexPathsForSelectedRows() {
for var i = 0; i < indexPaths.count; ++i {
var thisPath = (indexPaths as! [NSIndexPath])[i]
var cell = tableView.cellForRowAtIndexPath(thisPath)
if let cell = cell {
self.addedPoolarray.append(cell)
// Do something with the cell
// If it's a custom cell, downcast to the proper type
}
}
It's incorrect. UITableView re-use cells that means one cell will be run again with a different PFObject and a different indexPath.
Instead, you can populate those selected cells on another tableview controller in the prepareForSegue method:
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toRoutesVc" {
let destination = segue.destinationViewController as! RoutesVC
let indexPaths = self.tableView.indexPathsForSelectedRows()
/*
If the first table view controller
and the another table view controller have the same data source,
simply pass the indexPaths
*/
// destination.selectedObjects = indexPaths
/* Or pass the PFObjects: */
var selectedObjects = [PFObject]()
for indexPath in indexPaths {
selectedObjects.append(objects[indexPath.row])
}
destination.selectedObjects = selectedObjects
}
}
You need to create a custom controller (subclass of UITableViewController) with an array property that received from the first table view.
class RoutesVC: UITableViewController {
var selectedObjects: [AnyObject]?
...
}

Parse and Swift. Set tableViewCell accessory type from Parse relation

So, I have got a tableView which shows courses. The user is able to set Checkmarks on these courses (cells) and save them in his PFUser object as a relation to the Courses class (where all courses are stored).
My question is, how do I checkmark the courses a user has already saved at some point before.
This is my attempt, but I don’t know how to continue. How do I get the cells with a specific Label? (Or is there a better way?)
let courseRel = PFUser.currentUser()?.relationForKey("usercourses")
let query = courseRel!.query()
let qObjects :Array = query!.findObjects()!
println(qObjects)
for var qObjectsCount = qObjects.count; qObjectsCount > 0; --qObjectsCount {
var qAnObject: AnyObject = qObjects[qObjectsCount - 1]
var courseName = qAnObject["coursename"]
println(courseName)
if let cell: AnyObject? = tableView.dequeueReusableCellWithIdentifier("courseCell"){
}
}
EDIT: that code is in my override viewDidLoad
EDIT2:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("courseCell") as! PFTableViewCell!
if cell == nil {
cell = PFTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "courseCell")
}
let courseRel = PFUser.currentUser()?.relationForKey("usercourses")
let query = courseRel!.query()
let qObjects :Array = query!.findObjects()!
// Extract values from the PFObject to display in the table cell
if let courseName = object?["coursename"] as? String {
cell?.textLabel?.text = courseName
if contains(qObjects, object) {
cell?.accessoryType = UITableViewCellAccessoryType.Checkmark
}
}
return cell
}
Error in line ‚if contains(qObjects, object) {'
Generic parameter 'S.Generator.Element’ cannot be bound to non-#objc protocol type 'AnyObject'
EDIT3:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("courseCell") as! PFTableViewCell!
if cell == nil {
cell = PFTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "courseCell")
}
let courseRel = PFUser.currentUser()?.relationForKey("usercourses")
let query = courseRel!.query()
let qObjects :Array = query!.findObjects()!
// Extract values from the PFObject to display in the table cell
if let courseName = object?["coursename"] as? String {
cell?.textLabel?.text = courseName
cell.tintColor = UIColor.blackColor()
}
if contains(qObjects, { $0 === object }) {
cell?.accessoryType = UITableViewCellAccessoryType.Checkmark
self.selectedRows.addIndex(indexPath.row)
}else{
cell?.accessoryType = UITableViewCellAccessoryType.None
}
return cell
}
EDIT4: (Working code)
In the class:
// Initializing qObject variable
var qObjects :Array<AnyObject> = []
In my objectsDidLoad:
// Get PFObjects for the checkmarks on courses the currentUser has already selected before
let courseRel = PFUser.currentUser()?.relationForKey("usercourses")
let query = courseRel!.query()
qObjects = query!.findObjects()!
In my tableView(cellForRowAtIndexPath):
// Extract values from the PFObject to display in the table cell
if contains(qObjects, { $0 === object }) {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
self.selectedRows.addIndex(indexPath.row)
} else {
cell.accessoryType = UITableViewCellAccessoryType.None
}
Don't try to search for a cell. Well, you can, but not like you're trying to - I'll come back to that.
Your current code is creating new cells, not finding existing cells, so that won't work.
What you should really be doing is storing the array of returned objects, qObjects, and then when you're configuring the cell for display checking if that array contains the object for the current cell. If it does, tick it, otherwise remove the tick.
Now, if the load of qObjects happens after the view is shown you have 2 options:
reload the table view
update just the visible items
Option 2. is obviously better, especially if the user might be scrolling the list. To do that you want to use the array returned by calling indexPathsForVisibleRows on the table view. Then, iterate that list, get the associated object and check if it's in qObjects, then get the cell on display with cellForRowAtIndexPath: and update it.

Trying to pass core data from a tableView to a new view when row is selected - keep getting errors

I have been at this for a while - I am trying to pass data from a UITableView cell that uses core data to a new view. I have tried many different variables within the prepare for Segue code but keep getting use of unresolved identifier error - see code below - what am I missing?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "myEventsCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as MyEventsTableViewCell
let eventData = myEvents [indexPath.row]
cell.eventNameLabel.text = "Name: " + eventData.eventName
cell.startDateLabel.text = "Start: " + eventData.startDate
cell.endDateLabel.text = "End: " + eventData.endDate
cell.backgroundColor = UIColor.clearColor()
return cell
}
override func prepareForSegue (segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "showEventDetails" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let destinationController = segue.destinationViewController as eventDetailView
destinationController.eventNameLabel.text = "Name: " + eventData.eventName [indexPath.row]
destinationController.eventStartLabel.text = "Start: " + eventData.startDate [indexPath.row]
destinationController.eventEndLabel.text = "End: " + eventData.endDate [indexPath.row]
destinationController.eventDetailLabel.text = eventData.details [indexPath.row]
}
}
}
How does your segue know the object you want to transfer? You missed to call the object in a way like:
let thisTask = fetchedResultsController.objectAtIndexPath(indexPath!) as YOUROBJECT
or something else if you don't use NSFetchedResultController - for example: let eventData = myEvents [indexPath]
Don't know how does your model look like so it is maybe not so precise...but for sure you miss to tell the segue which object it is.
What is eventData in:
"Name: " + eventData.eventName [indexPath.row]
If your above code works why don't you use again the construct
let eventData = myEvents [indexPath.row]
and then
destinationController.eventNameLabel.text = "Name: " + eventData.eventName
/* code */
to access the values for your destination controller?
Since you didn't post your error, I don't know if this is what's causing the error, but you can't set the text of a label in your destination controller like you're trying to do. At the time prepareForSegue is called, the destination controller has been instantiated, but its view has not yet been loaded. Thus, its outlets will be nil. You should create string properties in your destination view controller, and pass the strings to it. Populate the labels in viewDidLoad.
I think your error has to do with eventData not being defined in prepareForSegue. A better approach to passing the data from the selected cell to the detail view controller is as follows:
// I assume you have an existing class eventData
class EventData {
var eventName: String!
// other properties
}
class MyEventsTableViewCell: UITableViewCell {
#IBOutlet var eventNameLabel: UILabel!
// other outlets
// Add this property to your tableview cell
// Now your cell has a direct reference to the eventData, we will use that in the prepareForSegue function
var eventData: EventData! {
didSet {
eventNameLabel.text = "Name: " + eventData.eventName
// other properties
}
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "myEventsCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as MyEventsTableViewCell
let eventData = myEvents [indexPath.row]
// Here you can set the eventData for the cell, this will automatically set the outlets.
cell.eventData = eventData
cell.backgroundColor = UIColor.clearColor()
return cell
}
override func prepareForSegue (segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "showEventDetails" {
// The sender parameter represents the selected cell
let selectedCell = sender! as MyEventsTableViewCell
// now we have a reference to the eventData for this selected cell
let eventData = selectedCell.eventData
let destinationController = segue.destinationViewController as eventDetailView
// we pass the eventData to the destinationController (you will have to add a eventData property to the destinationController)
destinationController.eventData = eventData
}
}
You should not directly access the outlets of the destinationController, as explained by #rdelmar. Instead create a property for eventData. When the destinationController view loads you can use that property to set the outlets of the detail view.
One more naming suggestion: your destination controller has class name eventDetailView. That is confusing, because it is not a view, but a viewcontroller. I would suggest to rename it to EventDetailViewController or something similar of your taste.

Resources