如何使补充视图在UICollectionView中浮动为UITableView普通样式的Section Headers

我努力实现与UICollectionView “浮动部分标题”效果。 在UITableViewUITableView默认行为)中已经足够容易了,在UICollectionView似乎是不可能的。 我是否错过了明显的?

苹果公司没有提供如何实现这一目标的文件。 看来,人们必须UICollectionViewLayout并实现一个自定义的布局才能达到这个效果。 这需要相当多的工作,实施以下方法:

重写的方法

每个布局对象应该实现以下方法:

 collectionViewContentSize layoutAttributesForElementsInRect: layoutAttributesForItemAtIndexPath: layoutAttributesForSupplementaryViewOfKind:atIndexPath: (if your layout supports supplementary views) layoutAttributesForDecorationViewOfKind:atIndexPath: (if your layout supports decoration views) shouldInvalidateLayoutForBoundsChange: 

然而,我不清楚如何使补充视图在单元格上方浮动,并“粘”到视图的顶部,直到到达下一个部分。 在布局属性中是否有这个标志?

我会使用UITableView但我需要创build一个相当复杂的集合层次结构,这很容易通过集合视图来实现。

任何指导或示例代码将不胜感激!

我正在寻找相同的效果,但在iOS9苹果是足够的UICollectionViewFlowLayout中添加一个简单的属性称为sectionHeadersPinToVisibleBounds

Apple文档参考

有了这个,你可以使标题浮动像桌面。

  let layout = UICollectionViewFlowLayout() layout.sectionHeadersPinToVisibleBounds = true layout.minimumInteritemSpacing = 1 layout.minimumLineSpacing = 1 super.init(collectionViewLayout: layout) 

要么实现下面的委托方法:

 – collectionView:layout:sizeForItemAtIndexPath: – collectionView:layout:insetForSectionAtIndex: – collectionView:layout:minimumLineSpacingForSectionAtIndex: – collectionView:layout:minimumInteritemSpacingForSectionAtIndex: – collectionView:layout:referenceSizeForHeaderInSection: – collectionView:layout:referenceSizeForFooterInSection: 

在你的视图控制器有你的:cellForItemAtIndexPath方法(只是返回正确的值)。 或者,也可以不使用委托方法,而是直接在布局对象中设置这些值,例如[layout setItemSize:size];

使用这些方法中的任何一种都可以让您在“代码”而不是“IB”中设置您的设置,因为它们在设置“自定义布局”时会被删除。 记得<UICollectionViewDelegateFlowLayout>在你的.h文件中join<UICollectionViewDelegateFlowLayout>

创build一个UICollectionViewFlowLayout的新子类,调用它任何你想要的,并确保H文件有:

 #import <UIKit/UIKit.h> @interface YourSubclassNameHere : UICollectionViewFlowLayout @end 

在实施文件里面确保它有以下内容:

 - (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]; NSIndexPath *firstObjectIndexPath = [NSIndexPath indexPathForItem:0 inSection:section]; NSIndexPath *lastObjectIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section]; UICollectionViewLayoutAttributes *firstObjectAttrs; UICollectionViewLayoutAttributes *lastObjectAttrs; if (numberOfItemsInSection > 0) { firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath]; lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath]; } else { firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:firstObjectIndexPath]; lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:lastObjectIndexPath]; } CGFloat headerHeight = CGRectGetHeight(layoutAttributes.frame); CGPoint origin = layoutAttributes.frame.origin; origin.y = MIN( MAX( contentOffset.y + cv.contentInset.top, (CGRectGetMinY(firstObjectAttrs.frame) - headerHeight) ), (CGRectGetMaxY(lastObjectAttrs.frame) - headerHeight) ); layoutAttributes.zIndex = 1024; layoutAttributes.frame = (CGRect){ .origin = origin, .size = layoutAttributes.frame.size }; } } return answer; } - (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound { return YES; } 

在界面生成器中为stream布局select“自定义”,select刚刚创build的“YourSubclassNameHere”类。 跑吧!

(注意:上面的代码可能不尊重contentInset.bottom值,特别是大的或小的页脚对象,或者有0对象但没有页脚的集合。

这是我的承诺,我觉得比上面的一瞥更简单。 简单的主要来源是我不是inheritancestream布局,而是滚动我自己的布局(如果你问我,更容易)。

请注意我假设你已经能够实现你自己的自定义UICollectionViewLayout将显示单元格和标题没有浮动实施。 一旦你写了这个实现,只有这样下面的代码才有意义。 同样,这是因为OP在特别询问浮动报头部分。

几个奖金:

  1. 我漂浮两个标题,不只是一个
  2. 标题将先前的标题排除在外
  3. 快看!

注意:

  1. supplementaryLayoutAttributes包含所有的头文件属性,没有实现浮动
  2. 我在prepareLayout使用了这个代码,因为我预先做了所有的计算。
  3. 不要忘记重写shouldInvalidateLayoutForBoundsChange为true!

 // float them headers let yOffset = self.collectionView!.bounds.minY let headersRect = CGRect(x: 0, y: yOffset, width: width, height: headersHeight) var floatingAttributes = supplementaryLayoutAttributes.filter { $0.frame.minY < headersRect.maxY } // This is three, because I am floating 2 headers // so 2 + 1 extra that will be pushed away var index = 3 var floatingPoint = yOffset + dateHeaderHeight while index-- > 0 && !floatingAttributes.isEmpty { let attribute = floatingAttributes.removeLast() attribute.frame.origin.y = max(floatingPoint, attribute.frame.origin.y) floatingPoint = attribute.frame.minY - dateHeaderHeight } 

如果你有一个单一的标题视图,你想固定到你的UICollectionView的顶部,这是一个相对简单的方法来做到这一点。 请注意,这是为了尽可能简单 – 它假定您在一个单独的部分中使用单个标题。

 //Override UICollectionViewFlowLayout class @interface FixedHeaderLayout : UICollectionViewFlowLayout @end @implementation FixedHeaderLayout //Override shouldInvalidateLayoutForBoundsChange to require a layout update when we scroll - (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return YES; } //Override layoutAttributesForElementsInRect to provide layout attributes with a fixed origin for the header - (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *result = [[super layoutAttributesForElementsInRect:rect] mutableCopy]; //see if there's already a header attributes object in the results; if so, remove it NSArray *attrKinds = [result valueForKeyPath:@"representedElementKind"]; NSUInteger headerIndex = [attrKinds indexOfObject:UICollectionElementKindSectionHeader]; if (headerIndex != NSNotFound) { [result removeObjectAtIndex:headerIndex]; } CGPoint const contentOffset = self.collectionView.contentOffset; CGSize headerSize = self.headerReferenceSize; //create new layout attributes for header UICollectionViewLayoutAttributes *newHeaderAttributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; CGRect frame = CGRectMake(0, contentOffset.y, headerSize.width, headerSize.height); //offset y by the amount scrolled newHeaderAttributes.frame = frame; newHeaderAttributes.zIndex = 1024; [result addObject:newHeaderAttributes]; return result; } @end 

请参阅: https : //gist.github.com/4613982

我遇到了同样的问题,并在我的谷歌search结果中发现了这一点。 首先我要感谢cocotutch分享他的解决scheme。 但是,我想让我的UICollectionView 水平滚动 ,并将标题粘贴到屏幕左侧,所以我不得不改变解决scheme。

基本上我只是改变了这一点:

  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 }; 

对此:

  if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 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 }; } else { CGFloat headerWidth = CGRectGetWidth(layoutAttributes.frame); CGPoint origin = layoutAttributes.frame.origin; origin.x = MIN( MAX(contentOffset.x, (CGRectGetMinX(firstCellAttrs.frame) - headerWidth)), (CGRectGetMaxX(lastCellAttrs.frame) - headerWidth) ); layoutAttributes.zIndex = 1024; layoutAttributes.frame = (CGRect){ .origin = origin, .size = layoutAttributes.frame.size }; } 

请参阅: https : //gist.github.com/vigorouscoding/5155703或http://www.vigorouscoding.com/2013/03/uicollectionview-with-sticky-headers/

我用严格的编码来运行这个程序。 但是,该代码不考虑sectionInset。

所以我改变了这个垂直滚动的代码

 origin.y = MIN( MAX(contentOffset.y, (CGRectGetMinY(firstCellAttrs.frame) - headerHeight)), (CGRectGetMaxY(lastCellAttrs.frame) - headerHeight) ); 

 origin.y = MIN( MAX(contentOffset.y, (CGRectGetMinY(firstCellAttrs.frame) - headerHeight - self.sectionInset.top)), (CGRectGetMaxY(lastCellAttrs.frame) - headerHeight + self.sectionInset.bottom) ); 

如果你们想要水平滚动的代码,请参考代码aove。

在cocotouch的post中有一个错误。 当节中没有项目,节页脚被设置为不显示时,节标题将超出收集视图,用户将无法看到它。

其实改变:

 if (numberOfItemsInSection > 0) { firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath]; lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath]; } else { firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:firstObjectIndexPath]; lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:lastObjectIndexPath]; } 

成:

 if (numberOfItemsInSection > 0) { firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath]; lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath]; } else { firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:firstObjectIndexPath]; lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:lastObjectIndexPath]; if (lastObjectAttrs == nil) { lastObjectAttrs = firstObjectAttrs; } } 

将解决这个问题。

如果已经在StoryboardXib文件中设置了stream布局,那么试试这个,

 (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionHeadersPinToVisibleBounds = true 

我在github上添加了一个很简单的例子,我想。

基本上,策略是提供一个自定义布局,使边界更改无效,并为拥有当前边界的补充视图提供布局属性。 正如其他人所build议的。 我希望代码是有用的。

如果有人在Objective-C中寻找解决scheme,把它放在viewDidload中:

  UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)_yourcollectionView.collectionViewLayout; [flowLayout setSectionHeadersPinToVisibleBounds:YES]; 

VCollectionViewGridLayout不粘头。 它是基于TLIndexPathTools的垂直滚动简单网格布局。 尝试运行Sticky Headers示例项目。

这个布局也比UICollectionViewFlowLayout有更好的批量更新animation行为。 提供了几个示例项目,让您在两种布局之间切换以展示改进。

Interesting Posts