欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

UICollectionView Sticky Section Header

程序员文章站 2022-07-14 18:40:52
...

UICollectionView Sticky Section Header

如果项目针对的是iOS9及其以后的系统,可直接使用系统的API来达到目的。在iOS9中,UICollectionViewFlowLayout类引入了2个属性,sectionHeadersPinToVisibleBoundssectionFootersPinToVisibleBounds,可以非常方便的做到固定不动的header和footer。

如果要兼容以前的iOS系统版本,就要自定义了,需继承UICollectionViewFlowLayout

以下的内容来自How to Add Sticky Section Headers to a Collection View

创建一个继承自UICollectionViewFlowLayout的类,重写如下的方法:

  • shouldInvalidateLayout(forBoundsChange:)
  • layoutAttributesForElements(in:)
  • layoutAttributesForSupplementaryView(ofKind:at:)

代码如下:

class StickyHeadersCollectionViewFlowLayout: UICollectionViewFlowLayout {

    // MARK: - Collection View Flow Layout Methods

    // bounds改变是否需要更新
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard let layoutAttributes = super.layoutAttributesForElements(in: rect) else { return nil }

        // Helpers
        let sectionsToAdd = NSMutableIndexSet() // 需要添加布局属性的section的集合
        var newLayoutAttributes = [UICollectionViewLayoutAttributes]() // 新的布局属性的数组

        for layoutAttributesSet in layoutAttributes {
            if layoutAttributesSet.representedElementCategory == .cell { //cell类型
                // Add Layout Attributes
                newLayoutAttributes.append(layoutAttributesSet)

                // Update Sections to Add
                sectionsToAdd.add(layoutAttributesSet.indexPath.section)

            } else if layoutAttributesSet.representedElementCategory == .supplementaryView { // 此处即header
                // Update Sections to Add
                sectionsToAdd.add(layoutAttributesSet.indexPath.section)
            }
        }

        for section in sectionsToAdd {
            let indexPath = IndexPath(item: 0, section: section)

            if let sectionAttributes = self.layoutAttributesForSupplementaryView(ofKind: UICollectionElementKindSectionHeader, at: indexPath) {
                newLayoutAttributes.append(sectionAttributes)
            }
        }

        return newLayoutAttributes
    }

    // 使header固定的布局计算逻辑
    override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let layoutAttributes = super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath) else { return nil }
        guard let boundaries = boundaries(forSection: indexPath.section) else { return layoutAttributes }
        guard let collectionView = collectionView else { return layoutAttributes }

        // Helpers
        let contentOffsetY = collectionView.contentOffset.y
        var frameForSupplementaryView = layoutAttributes.frame

        let minimum = boundaries.minimum - frameForSupplementaryView.height
        let maximum = boundaries.maximum - frameForSupplementaryView.height

        if contentOffsetY < minimum {
            frameForSupplementaryView.origin.y = minimum
        } else if contentOffsetY > maximum {
            frameForSupplementaryView.origin.y = maximum
        } else {
            frameForSupplementaryView.origin.y = contentOffsetY
        }

        layoutAttributes.frame = frameForSupplementaryView

        return layoutAttributes
    }

    // MARK: - Helper Methods

    func boundaries(forSection section: Int) -> (minimum: CGFloat, maximum: CGFloat)? {
        // Helpers
        var result = (minimum: CGFloat(0.0), maximum: CGFloat(0.0))

        // Exit Early
        guard let collectionView = collectionView else { return result }

        // Fetch Number of Items for Section 获取section有多少item
        let numberOfItems = collectionView.numberOfItems(inSection: section)

        // Exit Early
        guard numberOfItems > 0 else { return result }

        // 第一个item和最后一个item
        if let firstItem = layoutAttributesForItem(at: IndexPath(item: 0, section: section)),
           let lastItem = layoutAttributesForItem(at: IndexPath(item: (numberOfItems - 1), section: section)) {
            result.minimum = firstItem.frame.minY
            result.maximum = lastItem.frame.maxY

            // Take Header Size Into Account
            result.minimum -= headerReferenceSize.height
            result.maximum -= headerReferenceSize.height

            // Take Section Inset Into Account
            result.minimum -= sectionInset.top
            result.maximum += (sectionInset.top + sectionInset.bottom)
        }

        return result
    }

}

其效果如下:

UICollectionView Sticky Section Header

也推荐CSStickyHeaderFlowLayout


Customising an iOS UICollectionView Layout中,介绍了sticker footer,效果如下:

UICollectionView Sticky Section Header

源码地址,注意在Xcode9中运行会报错,参考UICollectionView exception in UICollectionViewLayoutAttributes from iOS7

Cell Prefetching有关,在storyboard中取消勾选即可