Section update event not being called from related entity in swift

混江龙づ霸主 提交于 2019-12-11 13:32:46

问题


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.


回答1:


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.




回答2:


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.




回答3:


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.



来源:https://stackoverflow.com/questions/37328774/section-update-event-not-being-called-from-related-entity-in-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!