views:

647

answers:

3

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

A: 

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.

Kendall Helmstetter Gelner
Thank you! That observation (the double updates) allowed my to come up with a workaround I can use.
Douglas Mayle
A: 

Can you post your workaround? It would be very helpful!

jp
A: 

I'm having the same problem. I find that didChangeSection fires off twice. Once when you create the object for insert, and once when you actually save it. To me, it shouldn't call didChangeSection until save is called. Or at the very least, willChangeSection would be called on creation of the object, and didChangeSection called when it is saved.

Now I'm looking into the NSManagedObjectContextDidSaveNotification observer method. This isn't part of the NSFetchedResultsControllerDelegate protocol, but you can register to receive it. Perhaps that will only be called when I actually call save and not before.

Brain2000