Reputation: 2141
I have a collectionview with 300 cells, driven by an NSFetchedResultsController. Every so often, all of the objects update, so I receive delegate messages telling me so, and I let the collection view handle the updates as I would a tableview. Unfortunately it locks the main thread for a few seconds every time this happens... I'm not sure why. Here's my code:
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.cascadeViewController.collectionView performBatchUpdates:^{
NSLog(@"performingBatchUpdates");
for (NSDictionary *change in self.changes) {
[change enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id obj, BOOL *stop) {
NSFetchedResultsChangeType type = [key unsignedIntegerValue];
if (type == NSFetchedResultsChangeInsert) {
[self.cascadeViewController.collectionView insertItemsAtIndexPaths:@[obj]];
} else if (type == NSFetchedResultsChangeDelete) {
[self.cascadeViewController.collectionView deleteItemsAtIndexPaths:@[obj]];
} else if (type == NSFetchedResultsChangeUpdate) {
[self.cascadeViewController.collectionView reloadItemsAtIndexPaths:@[obj]];
} else if (type == NSFetchedResultsChangeMove) {
[self.cascadeViewController.collectionView moveItemAtIndexPath:obj[0] toIndexPath:obj[1]];
}
}];
}
NSLog(@"performingBatchUpdates end");
} completion:^(BOOL finished) {
NSLog(@"completion");
// TODO: implement
// [self configureMessageAndFooterView];
}];
NSLog(@"end of method");
[self.changes removeAllObjects];
}
What's going on here? All 300 objects updating at once is not going to happen constantly in my app's real-life execution but enough that I need to worry about it. I'm using a stock UICollectionViewFlowLayout - do I need to do something more custom?
Upvotes: 2
Views: 2094
Reputation: 61
Had the same issue with performBatchUpdates:completion:
locking the main thread for seconds in a collection view of just ~100 elements.
After spending way too much time on the issue I found a solution: ensure the cell's size (as returned in -collectionView:layout:sizeForItemAtIndexPath:
or defined via the itemSize
property of your layout) has not a fractional value. I solved my performance issues by applying floor
on the computed height of my cells.
That being said, I have no idea why this happens. By looking at the stack trace of our profiled runs, a lot of time is spent in -[UIViewCollectionViewUpdate _computeGaps]
, which in turns invokes -[NSArray sortedArrayUsingSelector:]
hundreds or even thousands of times (as well as CFSortIndexes
, __CFSimpleMergeSort
…). By just using an integer value for the height of our cells, sortedArrayUsingSelector
is invoked less than 10 times and the whole process completes in a fraction of a second.
Upvotes: 5
Reputation: 9915
I vaguely recall seeing behavior like this before, but I don't have a solution for the NSFetchedResultsController
+ UICollectionViewFlowLayout
combo because we stopped using both of those classes due to a multitude of issues. You might consider checking out the alternatives we open sourced:
NSFetchedResultsController
. It provides a TLIndexPathController
class that is very similar to NSFetchedResultsController
except it also works with plain arrays and it can do animated sorting an filtering (unlike NSFetchedResultsController
. There are numerous sample projects, including a Core Data one.UICollectionViewFlowLayout
. It is a uniform, vertical scrolling grid, so it isn't as flexible as UICollectionViewFlowLayout
, but the animations are generally much better in most cases it does sticky headers (like UITableView
headers). There are a couple of sample projects that let you toggle between UICollectionViewFlowLayout
and VCollectionViewGridLayout
to see the improvement.We have an iPad app with a grid-like collection view containing around 1000 items and the above gives us great performance and nice smooth animation as our Core Data database updates in the background.
Upvotes: 0