Swift:Compositional Layout正交滚动导致sticky header失效问题
问题原因
当section的orthogonalScrollingBehavior不为.none时,该section会被包裹在独立的正交滚动容器中。默认情况下NSCollectionLayoutBoundarySupplementaryItem的pinToVisibleBounds = true仅会让header吸附在当前section的内部正交滚动容器顶部,而非整个UICollectionView的可视区域顶部。外层垂直滚动时整个section整体向上偏移,header会跟随section一起移出可视区域,就出现了你碰到的消失问题。
解决方案
方案1:自定义布局属性手动调整header位置(兼容iOS13+)
重写UICollectionViewCompositionalLayout的相关方法,手动修正需要吸顶的header的frame:
class StickyHeaderCompositionalLayout: UICollectionViewCompositionalLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var attributes = super.layoutAttributesForElements(in: rect) guard let collectionView = collectionView else { return attributes } // 筛选出需要吸顶的header的布局属性 let headerAttributes = attributes?.filter { $0.representedElementKind == "stickyHeader" } headerAttributes?.forEach { attr in guard let section = attr.indexPath.section as? YourSectionEnumType, case .liveEvents = section else { return } // 获取对应section的frame let sectionFrame = frame(forSection: attr.indexPath.section) // 计算header应该在的y坐标 let offsetY = collectionView.contentOffset.y + collectionView.contentInset.top let headerY = max(offsetY, sectionFrame.minY) // 保证header不会超过section的底部 let maxHeaderY = sectionFrame.maxY - attr.frame.height attr.frame.origin.y = min(headerY, maxHeaderY) // 提高zIndex避免被cell覆盖 attr.zIndex = 1024 } return attributes } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { // 滚动时实时刷新布局 return true } }
使用时直接用这个自定义布局代替系统默认的UICollectionViewCompositionalLayout即可,原有的section配置和header注册逻辑不需要改动。
方案2:使用全局布局补充元素(适配iOS14+,适合header样式统一的场景)
如果你的吸顶header样式统一,可以直接将header设置为整个collection view布局的全局边界补充元素,而不是属于某一个section:
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(Constants.timeFrameHeaderViewHeight)) let globalHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: "stickyHeader", alignment: .top) globalHeader.pinToVisibleBounds = true globalHeader.zIndex = 1024 let config = UICollectionViewCompositionalLayoutConfiguration() config.boundarySupplementaryItems = [globalHeader] let layout = UICollectionViewCompositionalLayout(sectionProvider: { sectionIndex, env in // 原有section的配置逻辑,不需要再给liveEvents section加header return yourSectionLayout }, configuration: config)
需要额外实现header的数据源方法,根据当前滚动位置判断需要展示的section内容即可。
额外注意点
- 无论用哪种方案,都需要给header设置大于0的zIndex,避免被滚动的cell覆盖
- 如果使用方案1,注意替换代码中的
YourSectionEnumType为你自己的section枚举类型,确保只处理需要吸顶的header
内容的提问来源于stack exchange,提问作者Peter Ševčík
相关产品推荐
相关产品推荐

