如何使补充视图在UICollectionView中浮动为UITableView普通样式的Section Headers
我努力实现与UICollectionView
“浮动部分标题”效果。 在UITableView
( UITableView
默认行为)中已经足够容易了,在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在特别询问浮动报头部分。
几个奖金:
- 我漂浮两个标题,不只是一个
- 标题将先前的标题排除在外
- 快看!
注意:
-
supplementaryLayoutAttributes
包含所有的头文件属性,没有实现浮动 - 我在
prepareLayout
使用了这个代码,因为我预先做了所有的计算。 - 不要忘记重写
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; } }
将解决这个问题。
如果已经在Storyboard
或Xib
文件中设置了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行为。 提供了几个示例项目,让您在两种布局之间切换以展示改进。