"DidChangeSection:" NSfetchedResultsController no se llama al método delegado

I have a standard split view controller, with a detail view and a table view. Pressing a button in the detail view can cause the an object to change its placement in the table view's ordering. This works fine, as long as the resulting ordering change doesn't result in a section being added or removed. I.e. an object can change it's ordering in a section or switch from one section to another. Those ordering changes work correctly without problems. But, if the object tries to move to a section that doesn't exist yet, or is the last object to leave a section (therefore requiring the section its leaving to be removed), then the application crashes.

NSFetchedResultsControllerDelegate has methods to handle sections being added and removed that should be called in those cases. But those delegate methods aren't being called for some reason.

The code in question, is boilerplate:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView beginUpdates];

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {

    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {    

    UITableView *tableView = self.tableView;

    switch(type) {

        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView endUpdates];

    [detailViewController.reminderView update];

Starting the application, and then causing the last object to leave a section results in the following output:

2011-01-08 23:40:18.910 Reminders[54647:207] willChangeContent
2011-01-08 23:40:18.912 Reminders[54647:207] didChangeObject
2011-01-08 23:40:18.914 Reminders[54647:207] didChangeContent
2011-01-08 23:40:18.915 Reminders[54647:207] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1145.66/UITableView.m:825
2011-01-08 23:40:18.917 Reminders[54647:207] Serious application error.  Exception was caught during Core Data change processing: Invalid update: invalid number of sections.  The number of sections contained in the table view after the update (5) must be equal to the number of sections contained in the table view before the update (6), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with userInfo (null)

As you can see, "willChangeContent", "didChangeObject" (moving the object in question), and "didChangeContent" were all called properly. Based on the Apple's NSFetchedResultsControllerDelegate documentation "didChangeSection" should have been called before "didChangeObject", which would have prevented the exception causing the crash.

So I guess the question is how do I assure that didChangeSection gets called?

¡Gracias de antemano por cualquier ayuda!

preguntado el 09 de enero de 11 a las 04:01

2 Respuestas

This problem was caused by using a transient attribute as the sectionNameKeyPath. When I instead stored the attribute used for the sectionNameKeyPath in the database, the problem went away. I don't know if there is a way to get the sections updated based on a NSFetchedResultsController content changes when using a transient attribute as a sectionNameKeyPath. For now I am considering this a limitation of transient attributes.

Respondido el 10 de enero de 11 a las 22:01

I am having a very similar issue in my project. The didChangeSection does not seem to get called when it is supposed to. I am using "objectID.URIRepresentation" for the sectionNameKeyPath since I want each object to be in its own section. I am testing in the iPhone 5.1 simulator. My FRC sorts based on value (increasing to decreasing), so if the value of an object changes, the order of my sections should change as well, but the didChangeSection function never gets called. DO you have any intuition as to why this may be happening? Thanks. - habas oxidado

I struggled also with this, specifying nil for the sectionNameKeyPath at first. When all the objects got deleted, I returned 0 for number of sections in the tableView's datasource method (fetchedObjects == 0), which broke the tableView. You should be specifying some kind of sectionNameKeyPath if you need this delegate method to be fired. This now seems apparent enough, just thought I'll share for those as wooden headed as me. - Schoob

I had a very similar problem, our difference was that you were using a transient attribute, whereas I was not using any attribute in the data model en absoluto - I had a property in the category only, with custom getter and setter for all the willAccess...didChange stuff. Everything seemed perfect, but didChangeSection would never be called. What solved for me was to finally create a transient attribute to represent it. I could then remove my custom setter and just slightly adapt my custom getter to use -primitiveValueForKey:. - Gobe

I am doing the same thing in my application (transient property in the sectionNameKeyPath) and am not seeing the problem you are experiencing. I am testing this on iOS 4.2.1... There is a known bug where you cant trust any of the FRC delegate callbacks in iOS 3.X, you have to do a full [tableView reloadData] in the controllerDidChangeContent: message. see the FRC documentation

I have tested going from an existing section with another entry in it to a nonexistent section as well as from a section with only one row to another nonexistent section.

Respondido el 12 de enero de 11 a las 04:01

Thanks for pointing this out Brent. I am on iOS 4.1, so it is very possible I was encountering a bug that has since been fixed. - robenkleene

do you get this same behavior in the simulator? have you tried later versions? (I have a selfish interest because I am depending on this feature and I don't want customers to report bugs to me ;) Sadly I have updated all my iDevices to the latest possible. - Brent Priddy

Hi Brent, the original problem was discovered and tested SOLO in the simulator. i.e. I have never tried the app on an actual device while I was experiencing this problem. FYI, I've now upgraded the SDK to 4.2 and ended up putting back the transient properties, and I am no longer experiencing any problems. So that is further evidence that this was an SDK issue in 4.1 - robenkleene

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.