如何固定UICollectionViewCompositionalLayout的整个第一分区?
实现固定整个第一分区在CollectionView顶部的方案
针对你的需求,将第一分区的完整内容嵌入到Section Header中,利用pinToVisibleBounds属性固定头部是最可靠的系统原生方案,具体实现步骤如下:
核心思路
把原本的第一分区内容,封装成一个自定义的Section Header视图(内部嵌入横向滚动的UICollectionView),然后给第一个实际业务分区(原第二分区)设置这个Header并开启固定,以此模拟整个分区固定在顶部的效果。
具体实现代码
1. 创建固定头部的自定义视图
这个视图内部包含一个横向滚动的CollectionView,复用你原本第一分区的布局逻辑:
class StickyFullSectionHeader: UICollectionReusableView { static let reuseID = "StickyFullSectionHeader" lazy var innerCollectionView: UICollectionView = { // 复用原第一分区的横向布局 let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(120), heightDimension: .absolute(90)) let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(120), heightDimension: .absolute(90)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) section.orthogonalScrollingBehavior = .continuous section.interGroupSpacing = 16 section.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16) let layout = UICollectionViewCompositionalLayout(section: section) let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.translatesAutoresizingMaskIntoConstraints = false cv.showsHorizontalScrollIndicator = false return cv }() override init(frame: CGRect) { super.init(frame: frame) addSubview(innerCollectionView) NSLayoutConstraint.activate([ innerCollectionView.topAnchor.constraint(equalTo: topAnchor), innerCollectionView.leadingAnchor.constraint(equalTo: leadingAnchor), innerCollectionView.trailingAnchor.constraint(equalTo: trailingAnchor), innerCollectionView.bottomAnchor.constraint(equalTo: bottomAnchor) ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
2. 配置主CollectionView的CompositionalLayout
调整分区结构,给第一个业务分区(原第二分区)添加固定头部:
func createMainLayout() -> UICollectionViewCompositionalLayout { return UICollectionViewCompositionalLayout { sectionIndex, _ in // 原第二至第六分区的布局逻辑(现在对应sectionIndex 0-4) let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(120), heightDimension: .absolute(90)) let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(120), heightDimension: .absolute(90)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) section.orthogonalScrollingBehavior = .continuous section.interGroupSpacing = 16 section.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16) // 给第一个业务分区添加固定头部(原第一分区内容) if sectionIndex == 0 { let headerSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(106) // 对应头部视图的实际高度 ) let header = NSCollectionLayoutBoundarySupplementaryItem( layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top ) header.pinToVisibleBounds = true // 开启固定 section.boundarySupplementaryItems = [header] } return section } }
3. 主CollectionView数据源配置
注册头部视图,并处理头部内部CollectionView的数据源:
// 注册头部视图 collectionView.register( StickyFullSectionHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: StickyFullSectionHeader.reuseID ) // 数据源方法:返回头部视图 func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { guard kind == UICollectionView.elementKindSectionHeader, indexPath.section == 0 else { return UICollectionReusableView() } let header = collectionView.dequeueReusableSupplementaryView( ofKind: kind, withReuseIdentifier: StickyFullSectionHeader.reuseID, for: indexPath ) as! StickyFullSectionHeader // 配置头部内部CollectionView的数据源和代理(复用原第一分区的逻辑) header.innerCollectionView.dataSource = self header.innerCollectionView.delegate = self header.innerCollectionView.reloadData() return header } // 区分主CollectionView和头部内部CollectionView的数据源方法 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { if collectionView === (self.collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionHeader).first as? StickyFullSectionHeader)?.innerCollectionView { // 返回原第一分区的item数量 return 8 } else { // 返回对应业务分区的item数量 return 6 } }
方案说明
- 优点:完全利用系统原生的
pinToVisibleBounds机制,滚动性能稳定,不会出现布局错乱或卡顿问题。 - 注意事项:需要调整原有的分区索引逻辑(原第一分区的内容移到头部,业务分区从index 0开始),同时要区分主CollectionView和头部内部CollectionView的数据源/代理逻辑。
内容的提问来源于stack exchange,提问作者Victor
相关产品推荐
相关产品推荐

