69

で「フローティングセクションヘッダー」効果を実現するのに苦労してい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:

ただし、補足ビューをセルの上に浮かせて、次のセクションに到達するまでビューの上部に「貼り付ける」方法は明確ではありません。レイアウト属性にこれに対するフラグはありますか?

私は使用していたでしょうがUITableView、コレクションビューで簡単に実現できるコレクションのかなり複雑な階層を作成する必要があります。

ガイダンスやサンプルコードをいただければ幸いです。

4

14 に答える 14

70

iOS9 では、Apple は親切にも、単純なプロパティをUICollectionViewFlowLayoutcalledに追加しましたsectionHeadersPinToVisibleBounds

これにより、ヘッダーをテーブル ビューのようにフロートさせることができます。

let layout = UICollectionViewFlowLayout()
layout.sectionHeadersPinToVisibleBounds = true
layout.minimumInteritemSpacing = 1
layout.minimumLineSpacing = 1
super.init(collectionViewLayout: layout)
于 2015-11-27T15:55:20.237 に答える
8

UICollectionView の上部にピン留めするヘッダー ビューが 1 つある場合は、比較的簡単な方法でそれを行うことができます。これはできるだけ単純にすることを意図していることに注意してください。単一のセクションで単一のヘッダーを使用していることを前提としています。

//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

于 2013-01-23T21:55:38.897 に答える
7

これが私の見解です。上で垣間見たものよりもずっと簡単だと思います。シンプルさの主な原因は、フロー レイアウトをサブクラス化するのではなく、独自のレイアウトをローリングすることです (私に言わせれば、はるかに簡単です)。

UICollectionViewLayoutフローティングを実装せずにセルとヘッダーを表示する独自のカスタムを既に実装できると想定していることに注意してください。その実装を作成したら、以下のコードが意味をなすようになります。繰り返しますが、これは、OP がフローティング ヘッダー部分について具体的に尋ねていたためです。

いくつかのボーナス:

  1. 1 つだけでなく 2 つのヘッダーをフローティングしています
  2. ヘッダーは前のヘッダーを邪魔にならないようにプッシュします
  3. ほら、すばやい!

ノート:

  1. supplementaryLayoutAttributesフローティングが実装されていないすべてのヘッダー属性が含まれています
  2. prepareLayoutすべての計算を前もって行うため、このコードを で使用しています。
  3. shouldInvalidateLayoutForBoundsChangetrueにオーバーライドすることを忘れないでください。

// 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
}
于 2014-11-26T21:31:03.823 に答える
3

@iPrabuは. _ sectionHeadersPinToVisibleBoundsInterface Builder でもこのプロパティを設定できることを付け加えておきます。

  1. ドキュメント ナビゲータでフロー レイアウト オブジェクトを選択します。(折りたたまれている場合は、まずエディターの左下隅にあるツールバー ボタンを使用して展開します。)

ドキュメント ナビゲーターでフロー レイアウト オブジェクトを選択します。

  1. ID インスペクタを開き、キー path sectionHeadersPinToVisibleBounds、タイプBoolean、チェックボックスをオンにして、ユーザー定義のランタイム属性を追加します。

Identity インスペクターでランタイム属性を設定します。

デフォルトのヘッダー ビューの背景は透明です。(部分的に) 不透明にするか、ぼかし効果ビューを追加することができます。

于 2018-11-24T13:39:09.567 に答える
2

私は同じ問題に遭遇し、Google の結果でこれを見つけました。まず、解決策を共有してくれた cocotutch に感謝します。ただし、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
            };
        }

参照: https://gist.github.com/vigorouscoding/5155703またはhttp://www.vigorouscoding.com/2013/03/uicollectionview-with-sticky-headers/

于 2013-03-13T20:21:49.497 に答える
1

かなりシンプルなサンプルをgithubに追加しました。

基本的な戦略は、境界の変更を無効にするカスタム レイアウトを提供し、現在の境界を保持する補足ビューのレイアウト属性を提供することです。他の人が示唆したように。コードがお役に立てば幸いです。

于 2013-06-04T16:45:53.360 に答える
0

VCollectionViewGridLayoutはスティッキー ヘッダーを行います。TLIndexPathToolsに基づく縦スクロールの単純なグリッド レイアウトです。Sticky Headersサンプル プロジェクトを実行してみてください。

このレイアウトは、 よりもはるかに優れたバッチ更新アニメーション動作も備えていますUICollectionViewFlowLayout。2 つのレイアウトを切り替えて改善を実証できるサンプル プロジェクトがいくつか用意されています。

于 2013-07-11T04:21:03.843 に答える