I have an iOS 7 app using Storyboards that has the following structure:
UITabBarController->UINavgiationController->UICollectionViewController
I use a custom subclass of UICollectionViewFlowLayout to layout the grid. Each cell contains a single UILabel.
I am trying to render a 5 column grid in the UICVC. There can be upwards of 800 rows (sections, as one row per section). I've based this loosely on Erica Sadun's example code from her iOS book.
The cells all have specific, set widths (2 different widths used). All cells have the same height. The grid is wider than the physical display, and so scrolls horizontally and vertically.
It's all working fine, but performance has been very poor for more than about 30 rows. The problem comes when trying to calculate the custom layout, specifically in the following method:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *attribs = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *attributes = [NSMutableArray array];
[attribs enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *currentLayout, NSUInteger idx, BOOL *stop)
{
NSString *layoutItemKey = [NSString stringWithFormat:@"%ld:%ld", (long)currentLayout.indexPath.section, (long)currentLayout.indexPath.item];
UICollectionViewLayoutAttributes *newLayout = [self.cachedLayoutAttributes objectForKey:layoutItemKey];
if (nil == newLayout)
{
newLayout = [self layoutAttributesForItemAtIndexPath:currentLayout.indexPath];
long section = currentLayout.indexPath.section;
long item = currentLayout.indexPath.item;
NSString *layoutItemKey = [NSString stringWithFormat:@"%ld:%ld", (long)section, (long)item];
[self.cachedLayoutAttributes setObject:newLayout forKey:layoutItemKey];
}
[attributes addObject:newLayout];
}];
return attributes;
}
The main delay here seems to come from a call to the iOS method:
layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath;
which is inside the layoutAttributesForItemAtIndexPath method:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGSize thisItemSize = [self sizeForItemAtIndexPath:indexPath];
CGFloat verticalOffset = [self verticalInsetForItemAtIndexPath:indexPath];
CGFloat horizontalOffset = [self horizontalInsetForItemAtIndexPath:indexPath];
if (self.scrollDirection == UICollectionViewScrollDirectionVertical)
attributes.frame = CGRectMake(horizontalOffset, verticalOffset, thisItemSize.width, thisItemSize.height);
else
attributes.frame = CGRectMake(verticalOffset, horizontalOffset, thisItemSize.width, thisItemSize.height);
return attributes;
}
Scrolling works fine to a point, then freezes for about 1.5 seconds while the next block of layout is calculated (always seems to be about 165 cells). As I'm caching everything, the next time the user scrolls performance is fine.
If I leave the cell widths to the UICollectionViewFlowLayout default everything flies, with no pauses.
To try to speed things up I have:
- Ensured all views are opaque
- Set the deceleration rate of the CollectionView to FAST
- Made - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds return NO
- I cache any UICollectionViewLayoutAttributes calculated
- I cache the first 50 rows on initialisation. There's no noticeable lag doing this, and it allows the initial scroll performance to be a bit better than it would have been
I've run out of ideas for squeezing more performance out of the UICollectionViewFlowLayout.
Can anyone suggest how I can improve the code?
Thanks
Darren.