I am using core data in my application which will store the downloaded data. When app downloads the data i will save those in privateQueueContext (NSPrivateQueueConcurrencyType)
As soon as core data save completes i should update the UI. To accomplish this i have defined NSFetchedResultsController delegate methods. As i expected those delegate methods are called when change occurs in core data.
In didChangeObject
method i am inserting new cell but it leads to crash when there was many objects inserted in core data and gives following error message
*** Assertion failure in -[UICollectionView _endItemAnimations] CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (379) must be equal to the number of items contained in that section before the update (325), plus or minus the number of items inserted or deleted from that section (52 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
I am assuming datasource changes before completing new cell insert animation.
I hope many people would have faced same issue and there are so many similar questions in stack overflow but none of the answer is working properly for me. Can you someone please help me out on this?
Inserting new object in Core data
for (NSDictionary *obj in books){
Book *book = [Book insertInManagedObjectContext:context];
book.udid = [obj objectForKey:@“id”]
}
[context performBlock:^{
NSError *error = nil;
if ([context save:&error]) {
NSLog(@“success”);
}
}];
Fetch results controller delegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
self.blockOperation = [NSBlockOperation new];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
__weak UICollectionView *collectionView = self.collectionView;
switch(type) {
case NSFetchedResultsChangeInsert:{
[self.blockOperation addExecutionBlock:^{
[collectionView insertItemsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForItem:newIndexPath.item + 1 inSection:newIndexPath.section]]];
}];
break;
}
default:
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.collectionView performBatchUpdates:^{
[self.blockOperation start];
} completion:nil];
}
Your performBlock
save takes too long. You can
simply save on the main thread. Should take a split second to save 500 books, and as there is nothing critical happening in the UI (user is waiting) there is no need to do it in a block.
use performBlockAndWait
instead. The table view will be updated only after the save is finished. The outcome for the user is very similar to saving on the main thread.
use the NSFetchedResultsControllerDelegate
without the block operation. Follow the fetched results controller template provided with Xcode. This will likely be slower than just saving on the main thread as the UI will get multiple updates.