Below is a link to download a simplified version of my app that has the exact same problem. The plus "Add" button at the top adds a new record that is set at name = 1, qty = 1, and section = 1. Selecting a Cell increments them all to the next number. You can see that both the name and qty update, but the section never updates until you quit the app and start it again.
DropBox Download Link
I have the following relationship setup in CoreData:
In in my TableViewController, I am creating my FetchRequestController (frc) with the following code:
func fetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Items")
let sortDesc1 = NSSortDescriptor(key: "catalog.sections.section", ascending: true)
let sortDesc2 = NSSortDescriptor(key: "isChecked", ascending: true)
let sortDesc3 = NSSortDescriptor(key: "catalog.name", ascending: true)
fetchRequest.sortDescriptors = [sortDesc1, sortDesc2, sortDesc3]
return fetchRequest
}
func getFCR() -> NSFetchedResultsController {
frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "catalog.sections.section" , cacheName: nil)
return frc
}
So as shown, I'm preforming the fetch request on the Item Entity, and sorting by attributes in both the Catalog and Sections entities. And specifically to my problem, I have the sections key in my frc as the section attribute in the Sections Entity (which is related through the Catalog Entity).
When I'm updating various parts of the Item or Catalog I see the table cell update correctly (i.e. the didChangeObject event is called)
But if I change the section it never updates unless I completely back out of the table and then reenter it. (i.e. the didChangeSection event is never called even though the section is changing)
Below is the code I'm using to edit a pre-existing Item Record.
func editItem() {
let item: Items = self.item!
item.qty = Float(itemQty.text!)
item.catalog!.name = Util.trimSpaces(itemName.text!)
item.catalog!.brand = Util.trimSpaces(itemBrand.text!)
item.catalog!.qty = Float(itemQty.text!)
item.catalog!.price = Util.currencyToFloat(itemPrice.text!)
item.catalog!.size = Util.trimSpaces(itemSize.text!)
item.catalog!.image = UIImageJPEGRepresentation(itemImage.image!, 1)
if (self.section != nil) {
item.catalog!.sections = self.section
}
do {
try moc.save()
} catch {
fatalError("Edit Item save failed")
}
if (itemProtocal != nil) {
itemProtocal!.finishedEdittingItem(self, item: self.item!)
}
}
Just to note, when I add in a new record into the Item Entity, the didChangeObject and didChangeSection events are both properly called. Only when editing them is didChangeSection getting skipped or missed.
Just for completion, below is my code I'm using for didChangeObject and didChangeSection.
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case NSFetchedResultsChangeType.Update:
self.tableView.reloadSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Move:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Update:
self.tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Insert:
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
case NSFetchedResultsChangeType.Move:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
When I googled this issue, I found that others have had problems similar to this and it seems to be a feature (or bug) of the frc and how Xcode handles relationships. Basically, the frc is only watching the Item Entity, and when the Section Entity changes, it doesn't register with the frc. People have suggested various hacks as well, but so far none of them seem to be working for me. Examples are to do something like this item.catalog.sections = item.catalog.sections
None of the examples had the section key as a related entity, so I'm not sure if that is why they aren't working for me.
So my question is if is there some way to tell didChangeSection to execute and send it the proper NSFetchedResultsChangeType? Or even better yet, is there some way to "encourage" the frc to notice what is happening in the Section Entity that is related to the Item Entity through the Catalog Entity.
After playing with this a little, it seems the didChangeSection is only fired if the first relationship named in the sectionNameKeyPath is directly modified (ie. in this case, if you create a new Catalog linked to the correct section, and set item.catalog = newCatalog). But I think that is too convoluted as a work-around.
One solution would be to change your FRC to fetch the Catalog objects instead of Items. Since they map one-one, the table view should retain the same structure. The key changes are:
func fetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Catalog")
let sortDesc1 = NSSortDescriptor(key: "sections.section", ascending: true)
let sortDesc2 = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDesc1, sortDesc2]
return fetchRequest
}
and
func getFCR() -> NSFetchedResultsController {
frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "sections.section" , cacheName: nil)
return frc
}
Then modify the references to frc to reflect this change:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableViewCell
let catalog: Catalog = frc.objectAtIndexPath(indexPath) as! Catalog
cell.nameLbl.text = "Item #\(catalog.name!)"
cell.qtyLbl.text = "Qty: \(catalog.items.qty!.stringValue)"
return cell
}
and
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let catalog: Catalog = frc.objectAtIndexPath(indexPath) as! Catalog
var qty: Int = Int(catalog.items.qty!)
qty = qty + 1
catalog.items.qty = qty
var name: Int = Int(catalog.name!)
name = name + 1
catalog.name = name
var sec: Int = Int(catalog.sections.section!)
sec = sec + 1
var section: Sections?
if (checkSectionName(sec, moc: self.moc) == false) {
let entityDesc = NSEntityDescription.entityForName("Sections", inManagedObjectContext: self.moc)
section = Sections(entity: entityDesc!, insertIntoManagedObjectContext: self.moc)
section!.section = sec
} else {
section = returnSection(sec, moc: self.moc)
}
catalog.sections = section
do {
try moc.save()
} catch {
fatalError("Edit item save failed")
}
}
Because you are directly modifying the sections property of the catalog object, this will trigger the didChangeSection method. This still feels to me like a bit of a hack, but since the FRC is not behaving as one would like, a hack might be a necessary evil.
I have run into a similar situation and I ended up creating a second NSFetchedResultsController for the other type, and just listened to it for change events and updated my view as appropriate. Not an ideal solution, since it requires some manual coordination and keeping of some meta data, but it does get the job done.
This is really an extension of the answer from #CharlesA
As you have found the FRC doesn't observe anything other than its associated entity for changes. This isn't a feature or a bug, it's an implementation detail because it covers a large percentage of usage and the code required to analyse the graph for arbitrary depth changes is complex. In addition to that the delegate callbacks for a single change merged into the context could be extremely complex and effectively impossible to apply to the table with animations.
So, you really need to change your approach. The answer from Charles using a second FRC requires that the second FRC is used to instruct the first FRC to re-execute it's fetch and the fully reload the table view. You need to re-execute the fetch because otherwise your data source is wrong and there's no other way to update it. Technically you could try to apply the changes to the table with animation, depending on what you know / guarantee about how the changes are made and saved to the context it could work. Like if the user can only change 1 section at a time and it's auto-saved. And that no compound changes are made on a background thread.
The answer from #pbasdf is also a suitable alternative, but it will still only deal with relationship changes, not changes to the values in the objects at the end of those relationships - this is a key distinction you need to understand and appreciate when using FRCs.
Related
something goes wrong when trying to update rows of tableview after delete of Firebase data.
Below is method I use.
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
let cell = self.messages[indexPath.row]
let b = cell.msgNo
let action = MyGlobalVariables.refMessages.child(MyGlobalVariables.uidUser!)
action.queryOrdered(byChild: "msgNo").queryEqual(toValue: b).observe(.childAdded, with: { snapshot in
if snapshot.exists() { let a = snapshot.value as? [String: AnyObject]
let autoId = a?["autoID"]
action.child(autoId as! String).removeValue()
self.messages.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
} else {
print("snapshot empty")
}}) }
...
return [delete, edit, preview]
}
Initially I checked whole logic without including line /*action.child(autoId as! String).removeValue()*/ then it works normally and removes rows as should be. But once I add this line it removes data from Firebase but tableview is updated in strange way by adding new rows below existing
My guess is that somewhere else in your application you have code like action .observe(.value, which shows the data in the table view. When you delete a node from the database, the code that populates the database gets triggered again, and it adds the same data (minus the node that you removed) to the table view again.
When working with Firebase it's best to follow the command query responsibility segregation principle, meaning that you keep the code that modifies the data completely separate from the flow that displays the data. That means that your code that deletes the data, should not try to update the table view. So something more like:
let action = MyGlobalVariables.refMessages.child(MyGlobalVariables.uidUser!)
action.queryOrdered(byChild: "msgNo").queryEqual(toValue: b).observe(.childAdded, with: { snapshot in
if snapshot.exists() { let a = snapshot.value as? [String: AnyObject]
let autoId = a?["autoID"]
action.child(autoId as! String).removeValue()
} else {
print("snapshot empty")
}}) }
All the above does is remove the selected message from the database.
Now you can focus on your observer, and ensuring it only shows the messages once. There are two options for this:
Always clear self.messages when your .value completion handler gets called before you add the messages from the database. This is by far the simplest method, but may cause some flicker if you're showing a lot of data.
Listen to the more granular messages like .childAdded and .childRemoved and update self.messages based on those. This is more work in your code, but will result in a smoother UI when there are many messages.
Here is my implementation of the delegate:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
ItemListTableViewController.logger.log("begin updates")
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
ItemListTableViewController.logger.log("end updates")
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
ItemListTableViewController.logger.log(type.rawValue, "\(indexPath) -> \(newIndexPath)")
switch type {
case .delete:
self.tableView.deleteRows(at: [indexPath!], with: .automatic)
case .insert:
self.tableView.insertRows(at: [newIndexPath!], with: .automatic)
case .move:
self.tableView.moveRow(at: indexPath!, to: newIndexPath!)
case .update:
self.tableView.reloadRows(at: [indexPath!], with: .automatic)
}
}
And here are the log I got:
ItemListTableViewController >>> "perform insert start"
ItemListTableViewController >>> "begin updates"
ItemListTableViewController >>> 1 "nil -> Optional([0, 0])"
ItemListTableViewController >>> 4 "Optional([0, 1]) -> Optional([0, 2])"
ItemListTableViewController >>> "end updates"
ItemListTableViewController >>> "perform insert end"
I'm trying to insert a new item into the context by calling context.insert(item), and a new item inserted according to the 3rd line of log. And the controller moves some item according to the 4th line. But the type of raw value '4' in NSFetchedResultsChangeType should be update but not move.
I also have tested for other cases, when I need to update an item, it gave me a move type.
Am I wrong about the meaning of the update and the move? Or it's a bug?
I was wrong. It was a bad code.
I'm using Xcode 8.3.1
Thank to #Joe Rose, I understand how this works. I'm not dealing with tasks that may insert and move in same time, so I didn't use your solution.
I was wrong about what update and move means. So I read Apple's doc again. It said:
Changes are reported with the following heuristics:
On add and remove operations, only the added/removed object is reported.
It’s assumed that all objects that come after the affected object are also moved, but these moves are not reported.
A move is reported when the changed attribute on the object is one of the sort descriptors used in the fetch request.
An update of the object is assumed in this case, but no separate update message is sent to the delegate.
An update is reported when an object’s state changes, but the changed attributes aren’t part of the sort keys.
So when move is reported, update is reported too. At that time, the fetched objects are updated, but tableView not, so I need to use indexPath to locate the cell, and newIndexPath to get the proper object.
This is my final solution and it behaves good for now:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
ItemListTableViewController.logger.log(type.rawValue, "\(indexPath) -> \(newIndexPath)")
switch type {
case .insert:
self.tableView.insertRows(at: [newIndexPath!], with: .automatic)
case .delete:
self.tableView.deleteRows(at: [indexPath!], with: .automatic)
case .move:
self.tableView.moveRow(at: indexPath!, to: newIndexPath!)
fallthrough
case .update:
self.configure(cell: tableView.cellForRow(at: indexPath!) as! ItemInfoCell, at: newIndexPath!)
}
}
func configure(cell: ItemInfoCell, at indexPath: IndexPath) {
let item = fetchController.object(at: indexPath)
cell.item = item
}
If there is any problem, please tell me.
4 is the enum value for update; 3 is the value for move. You may be confused by the two index paths that are being delegated. indexPath is the index before the update (before any inserts or deletes) and newIndexPath is the index after the updates. Unfortunately Apple's documentation is wrong when it come to how to update using a fetchedResults controller. For update you should be using newIndexPath since the updates are processed after the inserts and deletes. You may be experiencing crashes or bad behavior because of this.
Also the way you are dealing with move is incorrect. See App crashes after updating CoreData model that is being displayed in a UITableView
I have two VC with table views, the first showing categories, and the second showing items of selected category (recipes). I am able to get the RecipeTableVC to display filtered data using NSPredicate, but I haven't quite figured out how to delete the recipe from Core Data since the data displayed is a variable containing only the predicated data.
Here is my fetch:
func attemptRecipeFetch() {
let fetchRecipeRequest = NSFetchRequest(entityName: "Recipe")
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRecipeRequest.sortDescriptors = [sortDescriptor]
let controller = NSFetchedResultsController(fetchRequest: fetchRecipeRequest, managedObjectContext: ad.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedRecipeController = controller
do {
try self.fetchedRecipeController.performFetch()
let allRecipes = fetchedRecipeController.fetchedObjects as! [Recipe]
recipesOfCategory = allRecipes.filter { NSPredicate(format: "category = %#", selectedCategory!).evaluateWithObject($0) }
} catch {
let error = error as NSError
print("\(error), \(error.userInfo)")
}
}
So what's populating my table is the recipesOfCategory array.
Here is my attempt to delete so far:
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
recipesOfCategory.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
ad.managedObjectContext.delete(recipesOfCategory[indexPath.row])
}
}
This crashes and I understand why, but still haven't come up with a solution. Is there a way to implement swipe to delete where it deletes the recipe from Core Data? Am I using the correct methodology to populate the table with filtered data?
I used the following code to 'Swipe to delete from core data' in a table view for an App I recently did. I may work for you.
In your "tableView:commitEditingStyle",
1. set up CoreData access with ...
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext
2. Delete the desired row + incl. from Core Data...
if editingStyle == UITableViewCellEditingStyle.Delete {
context.deleteObject(self.resultsList[indexPath.row]) // Always before
as CoreD
self.resultsList.removeAtIndex(indexPath.row)
do {
try context.save()
} catch {
print("Error unable to save Deletion")
}
} // end IF EditingStyle
self.tableView.reloadData()
In your tableView:commitEditingStyle: you need to just delete the underlying object from Core Data, not from the table view. The NSFetchedResultsController delegate methods will tell you when to remove it from the table view.
I am using a tableView to display a list of people. I am trying to add an alert to confirm that the user actually wants to delete the person and to prevent mistakes. However, when I try to delete the person that is stored with CoreData, there seems to be a problem reloading the view. I get this exception:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Editing and Delete Function:
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
var deleteRow = indexPath.row
indexPathforDelete = indexPath
let entityDescription = NSEntityDescription.entityForName("People", inManagedObjectContext: managedObjectContext!)
let request = NSFetchRequest()
request.entity = entityDescription
var error: NSError?
var objects = managedObjectContext?.executeFetchRequest(request, error: &error)
if let results = objects {
let personToDelete = results[deleteRow] as! NSManagedObject
let firstName = personToDelete.valueForKey("firstName") as! String
let lastName = personToDelete.valueForKey("lastName") as! String
var message = "Are you sure you would like to delete \(firstName) \(lastName)?\nThis will permanentaly remove all records of "
if(personToDelete.valueForKey("gender") as! String == "Male"){
message = "\(message)him."
}
else{
println(personToDelete.valueForKey("gender") as! String)
message = "\(message)her."
}
var deleteAlert : UIAlertView = UIAlertView(title: "Delete \(firstName) \(lastName)", message: message, delegate: self, cancelButtonTitle: "Cancel")
deleteAlert.addButtonWithTitle("Delete")
deleteAlert.show()
}
save()
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
AlertView Response Function:
func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int){
if(buttonIndex == 1){
managedObjectContext?.deleteObject(personToDelete)
tableView.deleteRowsAtIndexPaths([indexPathforDelete], withRowAnimation: .Fade)
save()
}
setEditing(false, animated: true)
self.navigationItem.leftBarButtonItem = nil
}
tableView number of rows function:
var personToDelete = NSManagedObject()
var indexPathforDelete = NSIndexPath()
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
let entityDescription = NSEntityDescription.entityForName("People", inManagedObjectContext: managedObjectContext!)
let request = NSFetchRequest()
request.entity = entityDescription
var error: NSError?
var objects = managedObjectContext?.executeFetchRequest(request, error: &error)
let results = objects
println("Results Count: \(results!.count)")
return results!.count
}
I think the problem is that you have two variables with the name propertyToDelete: a property that you declare and initialise with a blank NSManagedObject:
var personToDelete = NSManagedObject()
and a local variable that you declare within your commitEditingStyle function:
let personToDelete = results[deleteRow] as! NSManagedObject
It is this local variable to which you assign the object from your results array. But this local variable is destroyed when the function completes, and the AlertView action is deleting the object to which the property points. (The reason I hesitate is that I would expect your context to throw an error when it tries to delete an object that has never been registered with it). Note that by contrast you have only the one variable named indexPathforDelete. This holds the correct value when the AlertView action runs, and consequently the tableView deletes the correct row. That's why you get the error: it has deleted a row, but then finds (because no object has been deleted) it still has the same number of rows as before.
The immediate solution is to use the property within your function, rather than a local variable: just delete let:
personToDelete = results[deleteRow] as! NSManagedObject
But I would also recommend rethinking your approach: you are repeating the same fetch. If all the datasource methods do the same, it will be repeated numerous times when the table view is first built, whenever a cell is scrolled into view, whenever a cell is tapped, etc. This will be costly in terms of performance. You should instead undertake the fetch once (perhaps in viewDidLoad), store the results in an array property, and use that for the table view datasource methods. Alternatively, and perhaps preferably, use an NSFetchedResultsController: it is very efficient and there is boilerplate code for updating the table view when objects are added or deleted.
The documentations of tableView:commitEditingStyle:forRowAtIndexPath: says: "You should not call setEditing:animated: within an implementation of this method. If for some reason you must, invoke it after a delay by using the performSelector:withObject:afterDelay: method."
I am working on a core data application and currently I have the methods setup correctly to save the primary object saves the name of the users deck but it doesn't save recall the secondary object even though the method used to save both is identical. The primary does save second though and I am wondering if it matters the order that objects are saved. I know it is a relational but I figured it wouldn't matter if the secondary was called to save prior to the primary. I am still new to core data so just a simple answer is enough. If I need to save the primary entity object first then I will build the app in such a way that such occurs, else I may have to relook at the code to figure out why it isn't recalling.
This is the code that is supposed to save prior to the name being saved in a relational manner:
#IBAction func buttonWarrior(sender: AnyObject) {
let entity = NSEntityDescription.entityForName("ClassSelection", inManagedObjectContext: classMOC!)
let newObject = ClassSelection(entity: entity!,insertIntoManagedObjectContext: classMOC)
newObject.classname = "Warrior"
var error: NSError?
classMOC?.save(&error)
if let err = error {
println(err)
} else {
self.performSegueWithIdentifier("popOver", sender: self)
}
}
This is the code used to store the primary object which is a different viewcontroller.swift file than the other one. This is presented as a popover box over the secondary object. This part works fine and recalls correctly :
#IBAction func enterButton(sender: AnyObject) {
let entityDescription = NSEntityDescription.entityForName("Deck",inManagedObjectContext: managedObjectContext!)
let storeDeck = Deck(entity: entityDescription!,insertIntoManagedObjectContext: managedObjectContext)
storeDeck.deckname = usersDeckName.text
var error: NSError?
managedObjectContext?.save(&error)
if let err = error {
status.text = err.localizedFailureReason
} else {
usersDeckName.text = ""
status.text = "Deck Saved"
self.performSegueWithIdentifier("showCardSelection", sender: self)
}
}
The recall method I am trying to use may not make sense in it's current iteration as I have been trying many different methods :
#IBOutlet weak var decksListed: UITableView!
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var savedDecksClass = [ClassSelection]()
var frc: NSFetchedResultsController = NSFetchedResultsController()
var frcClasses: NSFetchedResultsController = NSFetchedResultsController()
func getFetchedResultsController() -> NSFetchedResultsController {
frc = NSFetchedResultsController(fetchRequest: listFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
return frc
}
func getClassesFetchedResultsController() -> NSFetchedResultsController {
frcClasses = NSFetchedResultsController(fetchRequest: classFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
return frcClasses
}
func listFetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Deck")
let sortDescriptor = NSSortDescriptor(key: "deckname", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
return fetchRequest
}
func classFetchRequest() -> NSFetchRequest {
let fetchRequestClasses = NSFetchRequest(entityName: "Deck")
let classSortDescriptor = NSSortDescriptor(key: "classname", ascending: true)
fetchRequestClasses.sortDescriptors = [classSortDescriptor]
return fetchRequestClasses
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let numberofRowsInSection = frc.sections?[section].numberOfObjects
return numberofRowsInSection!
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("usersDeck", forIndexPath: indexPath) as! UITableViewCell
let listed = frc.objectAtIndexPath(indexPath) as! Deck
cell.textLabel?.text = listed.deckname
let listedClass = frcClasses.objectAtIndexPath(indexPath) as! ClassSelection
cell.detailTextLabel!.text = listedClass.classname
return cell
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
decksListed.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
frcClasses = getClassesFetchedResultsController()
frcClasses.delegate = self
frc.performFetch(nil)
frc = getFetchedResultsController()
frc.delegate = self
frc.performFetch(nil)
}
I hope this is enough to give you an idea. I checked the relationships out and they all seem to be correct in the model. I apologize in advanced for the way some of the code looks I plan on shrinking it down after all the editing is done and working.
Thanks to pbasdf for helping me with this one. The chat he opened actually contained exactly what was needed to be done. I just wasn't saving the relationship and passing the object from one view controller to the next. After showing me exactly how to do so with an example I figured out the rest! Basically it would never have been able to recall the object as it never knew that they were related....foolish me! Thanks again!