自定义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
相关产品推荐
相关产品推荐

