UICollectionView header header Stick to a specific Y

I am trying to replicate a scenario in which I have a collection view all the way to the top, a header view on top of the first line of the collection view, then a view of the header section (for indexPath.section == 1) that adheres to the title of the header without overlap or under it.

I implemented my own layout to make the title sticky. But I can’t get him to stick with the look of the header (so after his β€œorigin.y + height” he sticks to the top of the collection.

Here is my IB structure:

enter image description here

with custom layout:

enter image description here

Here's the perfect scenario:

Before scrolling:

enter image description here

After scrolling:

enter image description here

Here is my code:

@implementation LLCollectionCustomFlowLayout - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return YES; } - (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy]; NSMutableIndexSet *missingSections = [NSMutableIndexSet indexSet]; for (NSUInteger idx=0; idx<[answer count]; idx++) { UICollectionViewLayoutAttributes *layoutAttributes = answer[idx]; if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) { [missingSections addIndex:layoutAttributes.indexPath.section]; // remember that we need to layout header for this section } if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) { [answer removeObjectAtIndex:idx]; // remove layout of header done by our super, we will do it right later idx--; } } // layout all headers needed for the rect using self code [missingSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx]; UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath]; if (layoutAttributes != nil) { [answer addObject:layoutAttributes]; } }]; return answer; } - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath]; if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { UICollectionView * const cv = self.collectionView; CGPoint const contentOffset = cv.contentOffset; CGPoint nextHeaderOrigin = CGPointMake(INFINITY, INFINITY); if (indexPath.section+1 < [cv numberOfSections]) { UICollectionViewLayoutAttributes *nextHeaderAttributes = [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:[NSIndexPath indexPathForItem:0 inSection:indexPath.section+1]]; nextHeaderOrigin = nextHeaderAttributes.frame.origin; } CGRect frame = attributes.frame; if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { frame.origin.y = MIN(MAX(contentOffset.y, frame.origin.y), nextHeaderOrigin.y - CGRectGetHeight(frame)); } else { // UICollectionViewScrollDirectionHorizontal frame.origin.x = MIN(MAX(contentOffset.x, frame.origin.x), nextHeaderOrigin.x - CGRectGetWidth(frame)); } attributes.zIndex = 1024; attributes.frame = frame; } return attributes; } - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath]; return attributes; } - (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath]; return attributes; } @end 
+7
ios objective-c uicollectionview uicollectionviewlayout
source share
2 answers

I solved it sketchy:

  - (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy]; NSMutableIndexSet *missingSections = [NSMutableIndexSet indexSet]; for (NSUInteger idx=0; idx<[answer count]; idx++) { UICollectionViewLayoutAttributes *layoutAttributes = answer[idx]; if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) { [missingSections addIndex:layoutAttributes.indexPath.section]; // remember that we need to layout header for this section } if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) { [answer removeObjectAtIndex:idx]; // remove layout of header done by our super, we will do it right later idx--; } } // layout all headers needed for the rect using self code [missingSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx]; UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath]; if (layoutAttributes != nil) { [answer addObject:layoutAttributes]; } if (idx == 1) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath]; if (layoutAttributes != nil) { [answer addObject:layoutAttributes]; } } }]; return answer; } - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath]; if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { UICollectionView * const cv = self.collectionView; CGPoint const contentOffset = cv.contentOffset; CGPoint previousHeaderOrigin = CGPointMake(INFINITY, INFINITY); CGFloat previousSectionY = 0; if (indexPath.section == 1) { UICollectionViewLayoutAttributes *previousHeaderAttributes = [self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; previousSectionY = previousHeaderAttributes.frame.origin.y; } CGRect frame = attributes.frame; if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { if (indexPath.section == 0) { frame.origin.y = MAX(contentOffset.y, frame.origin.y); }else{ frame.origin.y = MAX(previousSectionY+frame.size.height, frame.origin.y); } } else { // UICollectionViewScrollDirectionHorizontal frame.origin.x = MIN(MAX(contentOffset.x, frame.origin.x), previousHeaderOrigin.x - CGRectGetWidth(frame)); } attributes.zIndex = 1024; attributes.frame = frame; } return attributes; } 

Also in the file where I have collectionView:

 - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{ if (section == 0) { return UIEdgeInsetsMake(-kHEIGHT_TOP_VIEW, 0, 0, 0); }else return UIEdgeInsetsMake(0, 0, 0, 0); } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { return CGSizeMake(self.collectionView.frame.size.width, -kHEIGHT_TOP_VIEW); } 

If you have a better answer, let me know so that I can accept yours and reward you with generosity.

+3
source share

The problem is here:

 frame.origin.y = MIN(MAX(contentOffset.y, frame.origin.y), nextHeaderOrigin.y - CGRectGetHeight(frame)); 

When the title is the last, nextHeaderOrigin = CGPointMake(INFINITY, INFINITY) . So frame.origin.y will be MAX(contentOffset.y, frame.origin.y) and stick to the top of the collection. You should update layoutAttributesForSupplementaryViewOfKind as follows:

 - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath]; if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { // for the header in the section #0, we have "sticky" behaviour by default if (indexPath.section == 0) { return attributes; } CGPoint contentOffset = self.collectionView.contentOffset; UICollectionViewLayoutAttributes *prevHeaderAttributes = [self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:[NSIndexPath indexPathForItem:0 inSection:indexPath.section - 1]]; CGFloat prevHeaderMaxY = CGRectGetMaxY(prevHeaderAttributes.frame); // The maximum Y value from the previous header should be the position we use to "stick" the current one CGFloat prevHeaderMaxX = CGRectGetMaxX(prevHeaderAttributes.frame); CGRect frame = attributes.frame; if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { frame.origin.y = MAX(frame.origin.y, prevHeaderMaxY); } else { // UICollectionViewScrollDirectionHorizontal frame.origin.x = MAX(frame.origin.x, prevHeaderMaxX); } attributes.frame = frame; } return attributes; } 
+4
source share

All Articles