“didChangeSection:” NSfetchedResultsController delegate method not being called

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.

  • Showing duplicate cells in UITableView via NSFetchedResultsController?
  • iOS 9 - “attempt to delete and reload the same index path”
  • How to unit-test NSFetchedResultsController in Swift
  • Changing a managed object property doesn't trigger NSFetchedResultsController to update the table view
  • NSFetchedResultsController attempting to insert nil object
  • Does NSFetchedResultsController Observe All Changes to Persistent Store?
  • The code in question, is boilerplate:

    - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    NSLog(@"willChangeContent");
        [self.tableView beginUpdates];
    }
    
    - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
               atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    NSLog(@"didChangeSection");
    
        switch(type) {
            case NSFetchedResultsChangeInsert:
                [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                break;
    
            case NSFetchedResultsChangeDelete:
                [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                break;
        }
    }
    
    - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
           atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
          newIndexPath:(NSIndexPath *)newIndexPath {    
    NSLog(@"didChangeObject");
    
        UITableView *tableView = self.tableView;
    
        switch(type) {
    
            case NSFetchedResultsChangeInsert:
                [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;
    
            case NSFetchedResultsChangeDelete:
                [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;
    
            case NSFetchedResultsChangeUpdate:
                [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
                break;
    
            case NSFetchedResultsChangeMove:
                [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
                break;
        }
    }
    
    - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    NSLog(@"didChangeContent");
        [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?

    Thanks in advance for any help!

    2 Solutions Collect From Internet About ““didChangeSection:” NSfetchedResultsController delegate method not being called”

    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.

    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.