如何制作 UICollectionView 全局页眉和/或页脚?

Pom*_*ule 1 uikit ios uicollectionview swift uicollectionviewcompositionallayout

我一直在尝试创建一个 UICollectionView 标头,它会粘在我的集合视图之上。我正在使用 UICollectionViewCompositionalLayout。

我尝试了多种方法:使用单元格,使用节标题并尝试使用插入和偏移量来正确定位它相对于我的内容......甚至在集合视图顶部添加一个视图来监听集合视图的滚动视图的 contentOffset 以将自身定位在正确的位置。但是这些方法都不能令人满意。他们都感觉像一个黑客。

我一直在做一些研究,显然你必须 sublcass UICollectionViewLayout 这非常乏味,而且只有一个标题似乎有点矫枉过正,但它对整个集合视图来说是全局的。

Pom*_*ule 7

TL; 博士

UICollectionViewCompositionalLayout有一个configuration属性,你可以通过创建一个UICollectionViewCompositionalLayoutConfiguration对象来设置它。这个对象有一些非常好的和有用的功能,比如boundarySupplementaryItems属性。

从文档:

与整个布局的边界边缘相关联的补充项的数组,例如全局页眉和页脚

答对了。设置此属性并在您的数据源中进行必要的连接,您应该拥有全局标头。

代码示例

在这里,我在我的布局中声明了一个全局标题。标题是视觉效果视图中的分段控件,但您的可以是UICollectionReusableView.

enum SectionLayoutKind: Int, CaseIterable {
    case description
}

private var collectionView: UICollectionView! = nil

override func viewDidLoad() {
    super.viewDidLoad()

    collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout())
}

static func descriptionSection() -> NSCollectionLayoutSection {
    // Instantiate and return a `NSCollectionLayoutSection` object.
}

func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout {
        (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
        // Create your section
        // add supplementaries such as header and footers that are relative to the section…
        guard let layoutKind = SectionLayoutKind(rawValue: sectionIndex) else { return nil }
        
        let section: NSCollectionLayoutSection
        switch layoutKind {
        case .description:
            section = Self.descriptionSection()
        }
        
        return section
    }
    
    /*
     ? Magic starts HERE:
    */
    let globalHeaderSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(44))
    Constants.HeaderKind.globalSegmentedControl, alignment: .top)
    let globalHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: globalHeaderSize, elementKind: Constants.HeaderKind.space, alignment: .top)
    // Set true or false depending on the desired behavior
    globalHeader.pinToVisibleBounds = true
    
    let config = UICollectionViewCompositionalLayoutConfiguration()
    /*
     If you want to do spacing between sections.
     That's another big thing this config object does.
     If you try to define section spacing at the section level with insets,
     the spacing is between the items and the standard headers.
    */
    config.interSectionSpacing = 20
    config.boundarySupplementaryItems = [globalHeader]
    layout.configuration = config
    /*
     End of magic. ?
    */
    
    return layout
}

struct Constants {
    struct HeaderKind {
        static let space = "SpaceCollectionReusableView"
        static let globalSegmentedControl = "segmentedControlHeader"
    }
}
Run Code Online (Sandbox Code Playgroud)

数据源部分补充代码:

let globalHeaderRegistration = UICollectionView.SupplementaryRegistration<SegmentedControlReusableView>(elementKind: Constants.HeaderKind.globalSegmentedControl) { (header, elementKind, indexPath) in
    // Opportunity to further configure the header
    header.segmentedControl.addTarget(self, action: #selector(self.onSegmentedControlValueChanged(_:)), for: .valueChanged)
}

dataSource.supplementaryViewProvider = { (view, kind, indexPath) in
    if kind == Constants.HeaderKind.globalSegmentedControl {
        return self.collectionView.dequeueConfiguredReusableSupplementary(using: globalHeaderRegistration, for: indexPath)
    } else {
        // return another registration object
    }
}
Run Code Online (Sandbox Code Playgroud)