Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionView Performance Problems on performBatchUpdates

We are trying to set up a UICollectionView with a custom layout. The content of each CollectionViewCell will be an image. Over all there will be several thousand images and about 140-150 being visible at one certain time. On an action event potentially all cells will be reorganized in position and size. The goal is to animate all the moving events currently using the performBatchUpdates method. This causes a huge delay time before everything gets animated.

This far we found out that internally the method layoutAttributesForItemAtIndexPath is called for every single cell (several thousand in total). Additionally, the method cellForItemAtIndexPath is called for more cells than can actually be displayed on the screen.

Are there any possibilities to enhance the performance of the animation?


The default UICollectionViewFlowLayout can't really offer the kind of design we want to realize in the app. Here's some of our code:

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
RPDataModel *dm = [RPDataModel sharedInstance]; //Singleton holding some global information
NSArray *plistArray = dm.plistArray; //Array containing the contents of the cells
NSDictionary *dic = plistArray[[indexPath item]];
RPCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CELL" forIndexPath:indexPath];
cell.label.text = [NSString stringWithFormat:@"%@",dic[@"name"]];
cell.layer.borderColor = nil;
cell.layer.borderWidth = 0.0f;
[cell loadAndSetImageInBackgroundWithLocalFilePath:dic[@"path"]]; //custom method realizing asynchronous loading of the image inside of each cell
return cell;
}

The layoutAttributesForElementsInRect iterates over all elements setting layoutAttributes for allthe elements within the rect. The for-statement breaks on the first cell being past the borders defined by the bottom-right corner of the rect:

-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray* attributes = [NSMutableArray array];
RPDataModel *dm = [RPDataModel sharedInstance];
for (int i = 0; i < dm.cellCount; i++) {
    CGRect cellRect = [self.rp getCell:i]; //self.rp = custom object offering methods to get information about cells; the getCell method returns the rect of a single cell
    if (CGRectIntersectsRect(rect, cellRect)) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:[dm.relevanceArray[i][@"product"] intValue] inSection:0];
        UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        attribute.size = cellRect.size;
        attribute.center = CGPointMake(cellRect.origin.x + attribute.size.width / 2, cellRect.origin.y + attribute.size.height / 2);
        [attributes addObject:attribute];
    } else if (cellRect.origin.x > rect.origin.x + rect.size.width && cellRect.origin.y > rect.origin.y + rect.size.height) {
        break;
    }
}
return attributes;
}

On Layout changes the results are pretty much the same no matter if the number of cells being defined in the layoutAttributesForElementsInRect is limited or not .. Either the system gets the layout attributes for all cells in there if it isn't limited or it calls the layoutAttributesForElementAtIndexPath method for all the missing cells if it is limited. Overall the attributes for every single cell is being used somehow.

-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
RPDataModel *dm = [RPDataModel sharedInstance];
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGRect cellRect = [self.rp getCell:[dm.indexDictionary[@(indexPath.item)] intValue]];
attribute.size = cellRect.size;
attribute.center = CGPointMake(cellRect.origin.x + attribute.size.width / 2, cellRect.origin.y + attribute.size.height / 2);
return attribute;
}    
like image 714
Kevin Wellhöfer Avatar asked Sep 24 '12 11:09

Kevin Wellhöfer


1 Answers

Without seeing code, my guess is that your layoutAttributesForElementsInRect method is iterating through all of the items in your collection, and that is, in turn, what is causing the other methods to get over-called. layoutAttributesForElementsInRect is giving you a hint - that is, the CGRect it is passing to you - about which items you need to call layoutAttributesForItemAtIndexPath for, in terms of what is on the screen.

So that may be part of the performance issue - that is, tuning your custom layout so it is being smart about which items it is updating.

The other issues pertain to animation performance in general. One thing to watch out for is if there is any sort of compositing going on - make sure your images are opaque. Another thing is if you're using shadows on your image, those can be expensive to animate. One way to improve the animation performance of shadows is set the shadowPath value when the image is resized - if you do have shadows, let me know and post some code for doing that.

like image 191
Rob Reuss Avatar answered Oct 11 '22 06:10

Rob Reuss