Last active
April 5, 2022 22:11
-
-
Save toblerpwn/5393460 to your computer and use it in GitHub Desktop.
Sticky Headers at the top of a UICollectionView! -- // -- http://stackoverflow.com/questions/13511733/how-to-make-supplementary-view-float-in-uicollectionview-as-section-headers-do-i -- // -- still needs work around contentInsets.bottom and oddly-sized footers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CustomCollectionFlowLayout.h | |
// evilapples | |
// | |
// http://stackoverflow.com/questions/13511733/how-to-make-supplementary-view-float-in-uicollectionview-as-section-headers-do-i | |
// | |
// | |
#import <UIKit/UIKit.h> | |
@interface CustomCollectionFlowLayout : UICollectionViewFlowLayout | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CustomCollectionFlowLayout.m | |
// evilapples | |
// | |
// http://stackoverflow.com/questions/13511733/how-to-make-supplementary-view-float-in-uicollectionview-as-section-headers-do-i | |
// | |
// | |
#import "CustomCollectionFlowLayout.h" | |
@implementation CustomCollectionFlowLayout | |
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { | |
NSMutableArray *answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy]; | |
UICollectionView * const cv = self.collectionView; | |
CGPoint const contentOffset = cv.contentOffset; | |
NSMutableIndexSet *missingSections = [NSMutableIndexSet indexSet]; | |
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) { | |
if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) { | |
[missingSections addIndex:layoutAttributes.indexPath.section]; | |
} | |
} | |
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) { | |
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]; | |
[answer addObject:layoutAttributes]; | |
}]; | |
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) { | |
if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) { | |
NSInteger section = layoutAttributes.indexPath.section; | |
NSInteger numberOfItemsInSection = [cv numberOfItemsInSection:section]; | |
NSIndexPath *firstObjectIndexPath = [NSIndexPath indexPathForItem:0 inSection:section]; | |
NSIndexPath *lastObjectIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section]; | |
BOOL cellsExist; | |
UICollectionViewLayoutAttributes *firstObjectAttrs; | |
UICollectionViewLayoutAttributes *lastObjectAttrs; | |
if (numberOfItemsInSection > 0) { // use cell data if items exist | |
cellsExist = YES; | |
firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath]; | |
lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath]; | |
} else { // else use the header and footer | |
cellsExist = NO; | |
firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader | |
atIndexPath:firstObjectIndexPath]; | |
lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter | |
atIndexPath:lastObjectIndexPath]; | |
} | |
CGFloat topHeaderHeight = (cellsExist) ? CGRectGetHeight(layoutAttributes.frame) : 0; | |
CGFloat bottomHeaderHeight = CGRectGetHeight(layoutAttributes.frame); | |
CGRect frameWithEdgeInsets = UIEdgeInsetsInsetRect(layoutAttributes.frame, | |
cv.contentInset); | |
CGPoint origin = frameWithEdgeInsets.origin; | |
origin.y = MIN( | |
MAX( | |
contentOffset.y + cv.contentInset.top, | |
(CGRectGetMinY(firstObjectAttrs.frame) - topHeaderHeight) | |
), | |
(CGRectGetMaxY(lastObjectAttrs.frame) - bottomHeaderHeight) | |
); | |
layoutAttributes.zIndex = 1024; | |
layoutAttributes.frame = (CGRect){ | |
.origin = origin, | |
.size = layoutAttributes.frame.size | |
}; | |
} | |
} | |
return answer; | |
} | |
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound { | |
return YES; | |
} | |
@end |
This seems to ignore the cell dimensions I specify in -(CGSize)collectionView:layout:sizeForItemAtIndexPath:
:(
thanks for your nice and clean code, there is a change need to be done:
// we don't want header be positioned out of CV bounds, we we check if lastObjectAttrs exists, else section header origin will be negative
if (lastObjectAttrs) {
origin.y = MIN(
MAX(
contentOffset.y + cv.contentInset.top,
(CGRectGetMinY(firstObjectAttrs.frame) - topHeaderHeight)
),
(CGRectGetMaxY(lastObjectAttrs.frame) - bottomHeaderHeight)
);
}
In my app I can expand or collapse a section via removing and deleting items.
so here is the result:
before: cause the header origin be negative and goes out of collection view bounds
after:
also remove zIndex = 1024 and add this section too:
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
// header of next section will have greater z position than previous section header so previous header shadow will be beneath next section header
attributes.zIndex = indexPath.section;
}
return attributes;
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Works great! Thanks for sharing ;)