Iphone 如何使辅助视图在UICollectionView中浮动,就像UITableView普通样式中的节标题一样

Iphone 如何使辅助视图在UICollectionView中浮动,就像UITableView普通样式中的节标题一样,iphone,uitableview,view,uicollectionview,supplementary,Iphone,Uitableview,View,Uicollectionview,Supplementary,我正在努力通过UICollectionView实现“浮动节头”效果。在UITableView(UITableViewStylePlain的默认行为)中非常简单的东西在UICollectionView中似乎不可能不经过大量的努力。我是否错过了显而易见的事情 苹果没有提供关于如何实现这一点的文档。似乎只有将UICollectionViewLayout子类化并实现自定义布局才能实现这种效果。这需要相当多的工作,实施以下方法: 重写的方法 每个布局对象都应实现以下方法: collectionViewCo

我正在努力通过
UICollectionView
实现“浮动节头”效果。在
UITableView
(UITableViewStylePlain的默认行为)中非常简单的东西在
UICollectionView
中似乎不可能不经过大量的努力。我是否错过了显而易见的事情

苹果没有提供关于如何实现这一点的文档。似乎只有将
UICollectionViewLayout
子类化并实现自定义布局才能实现这种效果。这需要相当多的工作,实施以下方法:

重写的方法

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

collectionViewContentSize
layoutAttributesForElementsInRect:
layoutAttributesForItemAtIndexPath:
layoutAttributesForSupplementaryViewOfKind:atIndexPath: (if your layout supports supplementary views)
layoutAttributesForDecorationViewOfKind:atIndexPath: (if your layout supports decoration views)
shouldInvalidateLayoutForBoundsChange:
但是,我不清楚如何使补充视图浮动在单元格上方,并“粘贴”到视图顶部,直到到达下一部分。布局属性中是否有此标志

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


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

实现以下委托方法之一:

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

使用这两种方法中的任何一种都将使您能够在代码中而不是IB中设置设置,因为在设置自定义布局时,这些设置将被删除。记住也要将
添加到.h文件中

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

#import <UIKit/UIKit.h>

@interface YourSubclassNameHere : UICollectionViewFlowLayout

@end
在Interface Builder中为流布局选择“Custom”,然后选择您刚刚创建的“YourSubclassNameHere”类。快跑


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

如果您有一个要固定到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

请参阅:

我遇到了同样的问题,并在我的谷歌搜索结果中发现了这个问题。首先,我要感谢Cocoutch分享他的解决方案。但是,我希望我的UICollectionView水平滚动,标题粘贴到屏幕的左侧,因此我不得不稍微更改解决方案

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

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

请参阅:或

我使用vigorouscoding的代码运行此程序。 然而,该代码没有考虑SECTIONSETION。

所以我更改了垂直滚动的代码

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

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

我添加了一个非常简单的示例

基本上,该策略是提供一个自定义布局,该布局在边界更改时失效,并为包含当前边界的补充视图提供布局属性。正如其他人所建议的那样。我希望代码有用。

不做粘性标题。这是一个基于垂直滚动的简单网格布局。尝试运行示例项目


此布局也比
UICollectionViewFlowLayout
具有更好的批更新动画行为。提供了几个示例项目,让您可以在两种布局之间切换以演示改进。

cocotouch的帖子中有一个bug。当节中没有项目且节页脚设置为不显示时,节页眉将超出集合视图,用户将无法看到它

事实上,变化:

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


将解决这个问题。

这是我对它的看法,我认为它比上面所看到的要简单得多。简单性的主要来源是,我没有对flow布局进行子类化,而是滚动我自己的布局(如果你问我的话,这要容易得多)

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

一些奖金:

  • 我浮动两个标题,而不是一个
  • 标题将前面的标题推到一边
  • 看,斯威夫特 注:

  • supplementaryLayoutAttributes
    包含所有未实现浮动的标题属性
  • 我在
    prepareLayout
    中使用了这段代码,因为我预先做了所有的计算
  • 不要忘记将
    应为边界设置验证布局
    覆盖为true
    //浮动它们的头
    让yOffset=self.collectionView!。米尼先生
    设headersRect=CGRect(x:0,y:y偏移,宽度:宽度,高度:headersHeight)
    var floatingAttributes=supplementaryLayoutAttributes.filter{
    $0.frame.minY0&!浮动属性{
    let attribute=floatingAttributes.removeLast()
    attribute.frame.origin.y=max(floatingPoint,attribute.frame.origin.y)
    floatingPoint=attribute.frame.minY-dateHeaderHeight
    }
    
    在iOS9中,苹果很友好地在中添加了一个名为的简单属性

    这样,就可以使标题像表视图中那样浮动

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

    如果已在
    情节提要
    Xib
    文件中设置流布局,请尝试此操作
    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;
        }
    }
    
    // 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
    }
    
    let layout = UICollectionViewFlowLayout()
    layout.sectionHeadersPinToVisibleBounds = true
    layout.minimumInteritemSpacing = 1
    layout.minimumLineSpacing = 1
    super.init(collectionViewLayout: layout)
    
    (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionHeadersPinToVisibleBounds = true
    
        UICollectionViewFlowLayout *flowLayout = 
        (UICollectionViewFlowLayout*)_yourcollectionView.collectionViewLayout;
        [flowLayout setSectionHeadersPinToVisibleBounds:YES];
    
    if let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
        layout.sectionHeadersPinToVisibleBounds = true
    }