I found a blog on how to make sticky headers,效果很好。唯一的问题是我不认为它考虑了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;
}
答案 0 :(得分:87)
最简单的iOS 9 +解决方案,因为它不需要编写UICollectionViewFlowLayout的子类。
在带有collectionView的viewController的viewDidLoad中使用以下代码:
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout // casting is required because UICollectionViewLayout doesn't offer header pin. Its feature of UICollectionViewFlowLayout
layout?.sectionHeadersPinToVisibleBounds = true
答案 1 :(得分:33)
由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
答案 2 :(得分:4)
这确实是一个很好的解决方案并且运作良好。但是,因为我们必须从shouldINvalidateLayoutForBoundsChange返回YES,所以每次视图滚动时这基本上都会调用prepareLayout。现在, IF 你的prepareLayout有责任创建布局属性,这很常见,这将极大地影响滚动性能。
一个对我有用的解决方案是不在prepareLayout中创建布局属性,而是在调用invalidateLayout之前显式调用的单独方法中执行。当它感觉需要知道布局时,UICollectionView会调用prepareLayout,因此这个解决方案也会处理这些情况。
答案 3 :(得分:2)
此代码适用于我
-(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;
}
试试这个家伙......
答案 4 :(得分:2)
您只需使用以下代码创建一个新的UICollectionViewFlowLayout:
class StickyHeaderLayout: UICollectionViewFlowLayout {
override init() {
super.init()
self.sectionFootersPinToVisibleBounds = true
self.sectionHeadersPinToVisibleBounds = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.sectionFootersPinToVisibleBounds = true
self.sectionHeadersPinToVisibleBounds = true
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
for attribute in attributes {
adjustAttributesIfNeeded(attribute)
}
return attributes
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let attributes = super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath) else { return nil }
adjustAttributesIfNeeded(attributes)
return attributes
}
func adjustAttributesIfNeeded(_ attributes: UICollectionViewLayoutAttributes) {
switch attributes.representedElementKind {
case UICollectionElementKindSectionHeader?:
adjustHeaderAttributesIfNeeded(attributes)
case UICollectionElementKindSectionFooter?:
adjustFooterAttributesIfNeeded(attributes)
default:
break
}
}
private func adjustHeaderAttributesIfNeeded(_ attributes: UICollectionViewLayoutAttributes) {
guard let collectionView = collectionView else { return }
guard attributes.indexPath.section == 0 else { return }
if collectionView.contentOffset.y < 0 {
attributes.frame.origin.y = collectionView.contentOffset.y
}
}
private func adjustFooterAttributesIfNeeded(_ attributes: UICollectionViewLayoutAttributes) {
guard let collectionView = collectionView else { return }
guard attributes.indexPath.section == collectionView.numberOfSections - 1 else { return }
if collectionView.contentOffset.y + collectionView.bounds.size.height > collectionView.contentSize.height {
attributes.frame.origin.y = collectionView.contentOffset.y + collectionView.bounds.size.height - attributes.frame.size.height
}
}
}
答案 5 :(得分:0)
以上都不适合我。我正在寻找一个干净的布局,照顾我的插图,给我一个Photo.app风格的滚动集合。
我修改了here解决方案来处理edgeInsets设置。为清楚起见,我在此附上完整的解决方案但是,您可以从以下要点获得完整的解决方案:#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