如何在UICollectionView中将Supplementary View float作为Section Headers在UITableView普通样式中进行

100*_*ams 65 iphone view supplementary uitableview uicollectionview

我正在努力实现"浮动节头"效果UICollectionView.如果没有大量的努力工作,那么在UITableView(默认行为UITableViewStylePlain)中很容易做到的事情似乎是不可能的UICollectionView.我错过了明显的吗?

Apple没有提供有关如何实现此目标的文档.似乎必须子类化UICollectionViewLayout并实现自定义布局才能实现此效果.这需要相当多的工作,实现以下方法:

覆盖的方法

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

collectionViewContentSize
layoutAttributesForElementsInRect:
layoutAttributesForItemAtIndexPath:
layoutAttributesForSupplementaryViewOfKind:atIndexPath: (if your layout supports supplementary views)
layoutAttributesForDecorationViewOfKind:atIndexPath: (if your layout supports decoration views)
shouldInvalidateLayoutForBoundsChange:
Run Code Online (Sandbox Code Playgroud)

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

我会使用UITableView但我需要创建一个相当复杂的集合层次结构,这可以通过集合视图轻松实现.

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

iPr*_*abu 65

在iOS9中,Apple非常友好地在UICollectionViewFlowLayout被调用中添加了一个简单的属性sectionHeadersPinToVisibleBounds.

有了这个,你可以使表头视图中的标题浮动.

let layout = UICollectionViewFlowLayout()
layout.sectionHeadersPinToVisibleBounds = true
layout.minimumInteritemSpacing = 1
layout.minimumLineSpacing = 1
super.init(collectionViewLayout: layout)
Run Code Online (Sandbox Code Playgroud)

  • 只需要一点修改:使用`yourCollectionView.collectionViewLayout`你需要将布局转换为`UICollectionViewFlowLayout`:`(self.yourCollectionView.collectionViewLayout as!UICollectionViewFlowLayout).sectionHeadersPinToVisibleBounds = true` (7认同)
  • 很棒的答案!我猜这上面的答案只是iOS 9之前的解决方案吗? (2认同)

top*_*ide 47

实现以下委托方法:

– collectionView:layout:sizeForItemAtIndexPath:
– collectionView:layout:insetForSectionAtIndex:
– collectionView:layout:minimumLineSpacingForSectionAtIndex:
– collectionView:layout:minimumInteritemSpacingForSectionAtIndex:
– collectionView:layout:referenceSizeForHeaderInSection:
– collectionView:layout:referenceSizeForFooterInSection:
Run Code Online (Sandbox Code Playgroud)

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

使用这些方法之一将使您能够在设置自定义布局时将代码设置为代码而不是IB.记得也要添加<UICollectionViewDelegateFlowLayout>到.h文件中!

创建一个新的子类UICollectionViewFlowLayout,随意调用它,并确保H文件具有:

#import <UIKit/UIKit.h>

@interface YourSubclassNameHere : UICollectionViewFlowLayout

@end
Run Code Online (Sandbox Code Playgroud)

在实现文件中,确保它具有以下内容:

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

}
Run Code Online (Sandbox Code Playgroud)

在Interface Builder中为Flow Layout选择"Custom",选择刚刚创建的"YourSubclassNameHere"类.跑!

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

  • 如果`numberOfItemsInSection == 0`,你将在这一行得到一个`EXC_ARITHMETIC代码= EXC_I386_DIV`(被零除?)崩溃:`UICollectionViewLayoutAttributes*firstCellAttrs = [self layoutAttributesForItemAtIndexPath:firstCellIndexPath];`.我还注意到这段代码的`contentOffset`部分不尊重我实现中的集合视图的顶部插入值(可能与上一期有关).将提出代码更改/ gist/etc. (3认同)
  • https://gist.github.com/toblerpwn/5393460 - 随意迭代,特别是关于gist中提到的限制.:) (3认同)

Mik*_*keV 7

如果您有一个标题视图,您想要固定到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
Run Code Online (Sandbox Code Playgroud)

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


Maz*_*yod 7

这是我的看法,我认为这比上面的瞥见简单得多.简单的主要来源是我不是子类化流程布局,而是滚动我自己的布局(如果你问我,会更容易).

请注意我假设您已经能够实现自己的自定义UICollectionViewLayout,它将显示单元格和标题而不实现浮动.一旦你编写了实现,只有这样,下面的代码才有意义.同样,这是因为OP专门询问浮动标题部分.

一些奖金:

  1. 我浮动两个标题,而不只是一个
  2. 标题推送前一个标题
  3. 看,快!

注意:

  1. supplementaryLayoutAttributes 包含所有未实现浮动的标头属性
  2. 我正在使用此代码prepareLayout,因为我提前完成所有计算.
  3. 别忘了覆盖shouldInvalidateLayoutForBoundsChange到真!

// 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
}
Run Code Online (Sandbox Code Playgroud)