UICollectionView Assertion failure
I ran into this very same problem when inserting the first cell into a collection view. I fixed the problem by changing my code so that I call the UICollectionView
- (void)reloadData
method when inserting the first cell, but
- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths
when inserting all other cells.
Interestingly, I also had a problem with
- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths
when deleting the last cell. I did the same thing as before: just call reloadData
when deleting the last cell.
Inserting section#0 just before inserting cells seems make UICollectionView happy.
NSArray *indexPaths = /* indexPaths of the cells to be inserted */NSUInteger countBeforeInsert = _cells.count;dispatch_block_t updates = ^{ if (countBeforeInsert < 1) { [self.collectionView insertSections:[NSIndexSet indexSetWithIndex:0]]; } [self.collectionView insertItemsAtIndexPaths:indexPaths];};[self.collectionView performBatchUpdates:updates completion:nil];
I've posted a work around for this issue here: https://gist.github.com/iwasrobbed/5528897
In the private category at the top of your .m
file:
@interface MyViewController (){ BOOL shouldReloadCollectionView; NSBlockOperation *blockOperation;}@end
Then your delegate callbacks would be:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller{ shouldReloadCollectionView = NO; blockOperation = [NSBlockOperation new];}- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type{ __weak UICollectionView *collectionView = self.collectionView; switch (type) { case NSFetchedResultsChangeInsert: { [blockOperation addExecutionBlock:^{ [collectionView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]]; }]; break; } case NSFetchedResultsChangeDelete: { [blockOperation addExecutionBlock:^{ [collectionView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]]; }]; break; } case NSFetchedResultsChangeUpdate: { [blockOperation addExecutionBlock:^{ [collectionView reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex]]; }]; break; } default: break; }}- (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: { if ([self.collectionView numberOfSections] > 0) { if ([self.collectionView numberOfItemsInSection:indexPath.section] == 0) { shouldReloadCollectionView = YES; } else { [blockOperation addExecutionBlock:^{ [collectionView insertItemsAtIndexPaths:@[newIndexPath]]; }]; } } else { shouldReloadCollectionView = YES; } break; } case NSFetchedResultsChangeDelete: { if ([self.collectionView numberOfItemsInSection:indexPath.section] == 1) { shouldReloadCollectionView = YES; } else { [blockOperation addExecutionBlock:^{ [collectionView deleteItemsAtIndexPaths:@[indexPath]]; }]; } break; } case NSFetchedResultsChangeUpdate: { [blockOperation addExecutionBlock:^{ [collectionView reloadItemsAtIndexPaths:@[indexPath]]; }]; break; } case NSFetchedResultsChangeMove: { [blockOperation addExecutionBlock:^{ [collectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; }]; break; } default: break; }}- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller{ // Checks if we should reload the collection view to fix a bug @ http://openradar.appspot.com/12954582 if (shouldReloadCollectionView) { [self.collectionView reloadData]; } else { [self.collectionView performBatchUpdates:^{ [blockOperation start]; } completion:nil]; }}
Credit for this approach goes to Blake Watters.