Daniel
Daniel

Reputation: 23359

NSFetchedResultsController delegate exception

I have an app which uses a table view to display a list of items form my core data. I am using a remote api and I am updating my content after pulling down the table view - this triggers a call to the API.

Data is retrieved, parsed and inserted/updated into my Core Data.

I am sometimes getting an error after saving my Core Data context... Note that I'm not using multiple threads for this and like I said it doesn't seem to always happen.

I am literally going quite mad. It seems this guy has a similar issue but I'm still unable to fix mine with his solution: CoreData error driving me crazy... CoreData: Serious application error. An exception caught from delegate of NSFetchedResultsController

Here is the full error:

2012-07-31 14:14:47.332 MyApp[2893:11303] 
*** Assertion failure in -[_UITableViewUpdateSupport _setupAnimationsForNewlyInsertedCells], 
/SourceCache/UIKit_Sim/UIKit-1914.84/UITableViewSupport.m:1133
2012-07-31 14:14:47.332 MyApp[2893:11303] CoreData: error: Serious application error.  
An exception was caught from the delegate of NSFetchedResultsController during a call to -
controllerDidChangeContent:.  
Attempt to create two animations for cell with userInfo (null)

UPDATE:

I have a predicate on my fetch request. In order to seem to be deleting objects previously downloaded from API and which are missing from the new JSON result. I am setting a hideFromUser flag, this is saved in my Core Data.

If this flag is YES then it doesn't appear in the table view. But if it's ok then it does. I am also updating info on that managed object should anything be changed. Is it possible that I have an object which was previously set to hide... and is now set to show, and it also had some new data, could this possible trigger a "cell should update" and a "cell should insert" ?

More I think about it less it seems to be relevant.

Here is how I am updating my data:

1) I set all relevant objects of the corresponding type to "hide form user" (NSPredicate ensures they don't show in Table View).

2) I get an NSArray from the JSON data.

3) Looping each item, my createABookOfClass:withJSON: method queries the core data for a book (using an ID from the json dictionary), if it doesn't find it, it creates a new one. Note: at this point the "hide from user flag" is reverted.

4) After all is done, I save.

[[DPLocalStore getInstance] hideFlagItemsOfType:NSStringFromClass([MyFavouriteBook class])];

NSArray * itemsJSON = [data mutableObjectFromJSONData];

[itemsJSON enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) {     
    [[DPLocalStore getInstance] createABookOfClass:[MyFavouriteBook class]
                                          withJSON:obj];
}];

NSError *error = nil;
BOOL didsave = [[DPLocalStore getInstance] save:&error];

Maybe what is happening is a cell containing Object A has been updated, it's update: the hide flag has changed. thus I am getting into a situation where the NSFetchedResultsController's delegate wants to update that cell, and delete it also... since the predicate now doesn't correspond to this object... That sound very likely...

Upvotes: 5

Views: 2831

Answers (1)

Daniel
Daniel

Reputation: 23359

I think I may have found the error, since I changed it I haven't experienced the problem so I am assuming that it's ok.

In my implementation of controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: I had this in the switch statement for NSFetchedResultsChangeMove:

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

    // Reloading the section inserts a new row and ensures that titles are updated appropriately.
    [tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section]
             withRowAnimation:UITableViewRowAnimationFade];
    break;

As it was used in an older project (which I was not part of), I assumed the entire controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: implementation was okay, since this really is more of a template rather then something you will do anything customised.

In consequence I didn't pay much attention to it until I was reading up about it again on the Apple developer site. And I noticed, in case of a NSFetchedResultsChangeMove you must delete a cell, and insert the cell at the new path. Yet I had implemented without really realising - delete the cell and reload the section !

In effect I should and now have the following:

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

Like I said I've not had a problem since. Would love to know from anyone what inner workings would be responsible for this - the exception having been "Attempt to create two animations for cell".

Thanks for any interest.

Upvotes: 10

Related Questions