Skip to content

Instantly share code, notes, and snippets.

@randomsequence
Last active October 26, 2016 14:57
Show Gist options
  • Save randomsequence/5772282 to your computer and use it in GitHub Desktop.
Save randomsequence/5772282 to your computer and use it in GitHub Desktop.
A subclass of UICollectionViewFlowLayout which has UITableView style sticky headers.
// StickyHeadersCollectionViewFlowLayout
//
// A subclass of UICollectionViewFlowLayout which has UITableView style sticky headers.
//
// This code is based on Evadne Wu's code^1, with the following changes:
//
// * Fixes a crash for sections with zero items
// * Adds support for UIScrollView's contentInset
// * Adds support for UICollectionViewFlowLayout's sectionInset
//
// [1]: http://blog.radi.ws/post/32905838158/sticky-headers-for-uicollectionview-using
@implementation StickyHeadersCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray * attributesArray = [[super layoutAttributesForElementsInRect: rect] mutableCopy];
UICollectionView * const cv = self.collectionView;
CGPoint const contentOffset = cv.contentOffset;
UIEdgeInsets contentInset = cv.contentInset;
UIEdgeInsets sectionInset = self.sectionInset;
NSMutableIndexSet *missingSections = [NSMutableIndexSet indexSet];
for (UICollectionViewLayoutAttributes *layoutAttributes in attributesArray) {
if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) {
[missingSections addIndex:layoutAttributes.indexPath.section];
}
}
for (UICollectionViewLayoutAttributes *layoutAttributes in attributesArray) {
if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
[missingSections removeIndex:layoutAttributes.indexPath.section];
}
}
[missingSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
[attributesArray addObject:layoutAttributes];
}];
for (UICollectionViewLayoutAttributes *layoutAttributes in attributesArray) {
if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
NSInteger section = layoutAttributes.indexPath.section;
NSInteger numberOfItemsInSection = [cv numberOfItemsInSection:section];
if (numberOfItemsInSection > 0) {
NSIndexPath *firstCellIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
NSIndexPath *lastCellIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section];
UICollectionViewLayoutAttributes *firstCellAttrs = [self layoutAttributesForItemAtIndexPath:firstCellIndexPath];
UICollectionViewLayoutAttributes *lastCellAttrs = [self layoutAttributesForItemAtIndexPath:lastCellIndexPath];
CGFloat headerHeight = CGRectGetHeight(layoutAttributes.frame);
CGPoint origin = layoutAttributes.frame.origin;
origin.y = MIN(
MAX(
contentOffset.y + contentInset.top,
(CGRectGetMinY(firstCellAttrs.frame) - headerHeight - sectionInset.top)
),
(CGRectGetMaxY(lastCellAttrs.frame) - headerHeight)
);
layoutAttributes.zIndex = 1024;
layoutAttributes.frame = (CGRect){
.origin = origin,
.size = layoutAttributes.frame.size
};
}
}
}
return attributesArray;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
@end
@jeremypiednoel
Copy link

Really nice. ty

@megakode
Copy link

I have made a fork which implements support for per section insets, using the UICollectionViewDelegateFlowLayout. You might want to consider updating your original code with my modification. (I can't make a pull request on a gist)

https://gist.github.com/megakode/08b66c102f09cd39ee24

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment