UICollectionView与一个粘性头

我发现了一个关于如何制作粘性标题的博客 ,它的效果很好。 唯一的事情是我不认为它考虑到sectionInserts。

这是如何打算看看:

在这里输入图像说明

我有我的插入:

collectionViewFlowLayout.sectionInset = UIEdgeInsetsMake(16, 16, 16, 16); 

粘头,它下移了16个像素:

在这里输入图像说明

我试着用原来的代码来思考,我认为问题在于最后一部分:

 layoutAttributes.frame = (CGRect){ .origin = CGPointMake(origin.x, origin.y), .size = layoutAttributes.frame.size 

如果我将其更改为origin.y - 16 ,标题将从正确的位置开始,但是当被推起时,头部的16个像素将脱离屏幕:

在这里输入图像说明

我不知道如何让它考虑sectionInsects。 任何人都可以帮忙吗?

以下是博客中的完整代码:

 - (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 *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, (CGRectGetMinY(firstCellAttrs.frame) - headerHeight) ), (CGRectGetMaxY(lastCellAttrs.frame) - headerHeight) ); layoutAttributes.zIndex = 1024; layoutAttributes.frame = (CGRect){ .origin = origin, .size = layoutAttributes.frame.size }; } } return answer; } 

由Todd Laney修复,以处理水平和垂直滚动,并考虑sectionInsets:

https://gist.github.com/evadne/4544569

 @implementation StickyHeaderFlowLayout - (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; } - (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound { return YES; } @end 

对于iOS 9 +最简单的解决scheme,因为它不需要编写UICollectionViewFlowLayout的子类。

在viewController中使用collectionView的viewDidLoad使用以下代码:

 let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout // casting is required because UICollectionViewLayout doesn't offer header pin. Its feature of UICollectionViewFlowLayout layout?.sectionHeadersPinToVisibleBounds = true 

这真的是一个很好的解决scheme,完美的作品。 但是,因为我们必须从shouldINvalidateLayoutForBoundsChange返回YES,所以每次视图滚动时都基本上调用prepareLayout。 现在, 如果您的prepareLayout负责创build布局属性,这很常见,这将极大地影响滚动性能。

一个解决scheme,对我来说,是不要在prepareLayout中创build布局属性,而是在调用invalidateLayout之前明确调用一个单独的方法。 当它感觉需要知道布局时,UICollectionView会调用prepareLayout,因此这个解决scheme也会处理这些情况。

这段代码适用于我

  -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy]; UICollectionView * const cv = self.collectionView; //CLS_LOG(@"Number of sections = %d", [cv numberOfSections]); CGPoint const contentOffset = cv.contentOffset; //CLS_LOG(@"Adding missing sections"); 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]; }]; NSInteger numberOfSections = [cv numberOfSections]; //CLS_LOG(@"For loop"); for (UICollectionViewLayoutAttributes *layoutAttributes in answer) { if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) { NSInteger section = layoutAttributes.indexPath.section; //CLS_LOG(@"Customizing layout attribute for header in section %d with number of items = %d", section, [cv numberOfItemsInSection:section]); if (section < numberOfSections) { 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; } 

试试这个家伙…

以上都没有为我工作。 我正在寻找一个干净的布局,照顾我的插图,给我一个Photo.app风格的滚动集合。

我调整了这里提出的解决scheme来照顾edgeInsets设置。 为了清楚起见,我在这里附上了完整的解 但是,您可以从以下要点获得完整的解决scheme: #3e1955a4492a897e677f 。

 @implementation SpringboardLayout - (id)init { if (self = [super init]) { self.headerReferenceSize = CGSizeMake(0, 50); self.footerReferenceSize = CGSizeMake(0, 0); self.sectionInset = UIEdgeInsetsMake(10, 10, 80, 10); self.scrollDirection = UICollectionViewScrollDirectionVertical; self.minimumInteritemSpacing = 10; self.minimumLineSpacing = 10; if(IS_IPHONE_6 || IS_IPHONE_6PLUS) { self.itemSize = CGSizeMake(100, 128); } else { self.itemSize = CGSizeMake(80, 108); } } return self; } - (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound { return YES; } - (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]; } else 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 - self.sectionInset.top)) ,(CGRectGetMaxY(lastObjectAttrs.frame) - bottomHeaderHeight + self.sectionInset.bottom)); layoutAttributes.zIndex = 1024; layoutAttributes.frame = (CGRect){ .origin = origin, .size = layoutAttributes.frame.size }; } } return answer; } @end