Until it starts crashing randomly, with cryptic error messages that don't make a lot of sense...
Wouldn't it be cool if somebody subclassed NSFetchedResultsController, and altered it so you can fix the problem with minimal changes while allowing you to keep your animations?
That's exactly what we did. Read on for more details.
The following applies to version 3.1 through 3.1.3 (the latest version at the time of this writing).
So what is going wrong and how can we fix it?
You have a table with sections, that starts out looking something like this:
(I overlaid the indexPath of each cell using photoshop. It is expressed as "[<section>, <row>]")
Then the user at [0, 0] becomes available, and the transition is *supposed* to end up like this:
But instead, your application crashes!
The error message is something confusing like
Serious application error. Exception was caught during Core Data change processing: *** -[NSCFArray objectAtIndex:]: index (1) beyond bounds (1) with userInfo (null)
The NSFetchedResultsController sent the following messages:
- controller:didChangeSection:atIndex:0 forChangeType:Insert
- controller:didChangeObject:atIndexPath:[0, 0] forChangeType:Update newIndexPath:nil
In other words, NSFRC processed the change as an Update instead of a Move. It got confused because the previousIndexPath and newIndexPath were the same.
If you catch the mistake, and process the change as a move then everything works as expected.
The same problem occurs if we reverse the change above. This time the crash message is different:
Serious application error. Exception was caught during Core Data change processing: Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (4) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted). with userInfo (null)
Again, the NSFRC sent the change as an update rather than a move. Processing the change as a move will fix the problem.
Now the above solution should fix the problem IF you are dealing with only a single change. The situation gets out of hand really fast if there are multiple inserted or deleted sections. I created a test app with several unit tests, but was unable to support animation in every single test case. There were some situations that required specific knowledge of the application architecture or of the underlying data. For these cases, I created a new delegate method.
How to use the code:
First download and add the SafeFetchedResultsController class to your project.
Then change your code from this:
fetchedResultsController = [[NSFetchedResultsController alloc] ...
fetchedResultsController.delegate = self;
And then simply add a new delegate method:
fetchedResultsController = [[SafeFetchedResultsController alloc] ...
fetchedResultsController.safeDelegate = self;
- (void)controllerDidMakeUnsafeChanges:(NSFetchedResultsController *)controller
And that's it! You're done!
Download the SafeFetchedResultsController class and test project.