You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义UICollectionViewLayout添加Section Header仅显示1个且重叠问题

问题根因

  • 所有section的header都被硬编码了y=0的坐标,导致多个header重叠在顶部,只能看到最上层的一个
  • 计算cell布局的时候没有预留header的高度,导致cell和header重叠
  • 没有缓存header的布局属性,每次查询属性时临时生成固定位置的错误属性

修复步骤

1. 新增header相关变量

在PinterestLayout类中新增header高度配置和header布局缓存:

class PinterestLayout: UICollectionViewLayout {
    // 原有变量保持不变
    weak var delegate: PinterestLayoutDelegate?
    var numberOfColumns = 2
    private let cellPadding: CGFloat = 6
    private var cache: [UICollectionViewLayoutAttributes] = []
    private var contentHeight: CGFloat = 0
    
    // 新增:header高度配置
    var headerHeight: CGFloat = 100
    // 新增:header布局属性缓存
    private var headerCache: [UICollectionViewLayoutAttributes] = []

    // ... 原有其他变量保持不变
}

2. 重写prepare()方法,提前计算每个header的位置

在处理每个section的cell之前,先计算当前section header的坐标,更新全局高度和偏移量,避免cell和header重叠:

override func prepare() {
    guard
        cache.isEmpty == true,
        let collectionView = collectionView
    else {
        return
    }
    // 每次计算前清空旧缓存
    headerCache.removeAll()
    contentHeight = 0
    
    let columnWidth = contentWidth / CGFloat(numberOfColumns)
    var xOffset: [CGFloat] = []
    for column in 0..<numberOfColumns {
        xOffset.append(CGFloat(column) * columnWidth)
    }
    var column = 0
    var yOffset: [CGFloat] = .init(repeating: 0, count: numberOfColumns)
    
    for section in 0..<collectionView.numberOfSections {
        // 先处理当前section的header
        let headerIndexPath = IndexPath(item: 0, section: section)
        let headerAttr = UICollectionViewLayoutAttributes(
            forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
            with: headerIndexPath
        )
        // header的y坐标取当前所有列的最大偏移量
        let currentMaxY = yOffset.max() ?? 0
        headerAttr.frame = CGRect(
            x: 0,
            y: currentMaxY,
            width: contentWidth,
            height: headerHeight
        )
        headerCache.append(headerAttr)
        // 更新全局内容高度
        contentHeight = headerAttr.frame.maxY
        // 所有列的偏移量更新为header底部,后续cell从header下方开始排布
        for i in 0..<numberOfColumns {
            yOffset[i] = headerAttr.frame.maxY
        }
        column = 0
        
        // 原有处理section内cell的逻辑保持不变
        for item in 0..<collectionView.numberOfItems(inSection: section) {
            let indexPath = IndexPath(item: item, section: section)
            let photoHeight = delegate?.collectionView(
                collectionView,
                heightForPhotoAtIndexPath: indexPath
            ) ?? 180
            let height = cellPadding * 2 + photoHeight
            let frame: CGRect
            
            if section == 0 {
                frame = CGRect(
                    x: 0,
                    y: yOffset[column],
                    width: contentWidth,
                    height: height
                )
                contentHeight = frame.maxY
                for column in 0..<numberOfColumns {
                    yOffset[column] = frame.maxY
                }
                column = 0
            } else {
                frame = CGRect(
                    x: xOffset[column],
                    y: yOffset[column],
                    width: columnWidth,
                    height: height
                )
                contentHeight = max(contentHeight, frame.maxY)
                yOffset[column] = yOffset[column] + height
                column = column < (numberOfColumns - 1) ? (column + 1) : 0
            }
            
            let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            attributes.frame = insetFrame
            cache.append(attributes)
        }
    }
}

3. 修改布局属性查询方法,从缓存读取header属性

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    var visibleAttributes: [UICollectionViewLayoutAttributes] = []
    // 添加可视区域内的header
    visibleAttributes.append(contentsOf: headerCache.filter { $0.frame.intersects(rect) })
    // 添加可视区域内的cell
    visibleAttributes.append(contentsOf: cache.filter { $0.frame.intersects(rect) })
    return visibleAttributes
}

override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    return headerCache[indexPath.section]
}

4. 新增布局重置逻辑,支持刷新布局

override func invalidateLayout() {
    super.invalidateLayout()
    cache.removeAll()
    headerCache.removeAll()
    contentHeight = 0
}

注意事项

需要确保你已在UICollectionView相关代理中注册了header的重用标识,否则header不会被正常渲染。


内容的提问来源于stack exchange,提问作者bao le

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 05:15:03