R – How to handle section inserts with an NSFetchedResultsController

core-dataiphonensfetchedresultscontroller

I've got an NSFetchedResultsController as my data source and and I implement NSFetchedResultsControllerDelegate in my custom UITableViewController. I'm using sectionNameKeyPath to break my result set into multiple sections.

In one of my methods, I'm adding a couple of objects to the context, all of which are in a new section. At the moment where I save the the objects, the delegate methods are called properly. The order of events:

// -controllerWillChangeContent: fires
[self.tableView beginUpdates]; // I do this

// -controller:didChangeSection:atIndex:forChangeType: fires for section insert
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]];

// -controller:didChangeObject:atIndexPath:forChangeType:newIndexPath fires many times
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                withRowAnimation:UITavleViewRowAnimationFade]; // for each cell

// -controllerDidChangeContent: fires after all of the inserts
[self.tableView endUpdates];  // <--- Where things go terribly wrong!!!

On the last call, "endUpdates", the application always crashes with:

Serious application error.  Exception was caught during Core Data change processing:
[NSCFArray objectAtIndex:]: index (5) beyond bounds (1) with userInfo (null)

It seems that the table updates are not in sync with the NSFetchedResultsController data in some way, and things blow up. I'm following the docs on NSFetchedResultsControllerDelegate, but it's not working. What's the right way to do it?

UPDATE: I've created a test project that exhibits this bug. You can download it at: NSBoom.zip

Best Answer

Tracing through the app, I note didChangeSection is called first, which inserts a whole section - and then didChangeObject is called repeatedly.

The problem is that in didChangeSection you insert a whole section, then right after before the table view is updated you are adding objects to that same section. This is basically a case of overlapping updates... (not allowed even in a begin/end updates block).

If you comment out the individual object insert, it all works:

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

If you comment out the section insert:, it doesn't work - but I have had less luck with insertRowsInSections working all the time, and it may well be because there is no section yet (which I'm sure is why you were inserting the section to begin with). You may have to detect either case to do inserts with the right granularity.

In general I've had a lot more luck reloading and inserting whole sections than rows, the table view seems very fiddly to me around those working. You can also try UITableViewRowAnimationNone which seems to operate successfully more often.

Related Topic