You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何实现不等高UICollectionView Item的居中布局?

解决UICollectionView瀑布流Item居中及消除上下空白问题

我帮你调整了DynamicLayout的代码,既保留了Pinterest瀑布流的核心逻辑,又实现了布局水平居中、消除Item上下不必要空白的效果。下面是修改后的完整代码,以及关键调整点的说明:

修改后的完整代码

class DynamicLayout: UICollectionViewFlowLayout {
    weak var delegate: DynamicLayoutDelegate? // 改为weak避免循环引用
    var numberOfColumns = 2
    fileprivate var cellHorizontalPadding: CGFloat = 6 // 仅保留水平方向间距
    fileprivate var contentHeight: CGFloat = 0
    
    fileprivate var contentWidth: CGFloat {
        guard let collectionView = collectionView else { return 0 }
        return collectionView.bounds.width - (collectionView.contentInset.left + collectionView.contentInset.right)
    }
    
    fileprivate var cache = [UICollectionViewLayoutAttributes]()
    
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }
    
    override func prepare() {
        guard cache.isEmpty, let collectionView = collectionView else { return }
        
        // 1. 计算列宽与水平偏移,确保整体布局居中
        let totalHorizontalSpacing = cellHorizontalPadding * CGFloat(numberOfColumns - 1)
        let columnWidth = (contentWidth - totalHorizontalSpacing) / CGFloat(numberOfColumns)
        // 如果需要固定列宽为310,替换上面两行:
        // let columnWidth: CGFloat = 310
        // let totalHorizontalSpacing = cellHorizontalPadding * CGFloat(numberOfColumns - 1)
        // let totalColumnsWidth = columnWidth * CGFloat(numberOfColumns) + totalHorizontalSpacing
        // let leftInset = (contentWidth - totalColumnsWidth) / 2
        
        var xOffset = [CGFloat]()
        for column in 0..<numberOfColumns {
            // 若用固定列宽,改为:xOffset.append(leftInset + CGFloat(column) * (columnWidth + cellHorizontalPadding))
            xOffset.append(CGFloat(column) * (columnWidth + cellHorizontalPadding))
        }
        
        var yOffset = [CGFloat](repeating: 0, count: numberOfColumns)
        var column = 0 // 初始化列索引
        
        // 2. 遍历所有Item计算布局属性
        for item in 0..<collectionView.numberOfItems(inSection: 0) {
            let indexPath = IndexPath(item: item, section: 0)
            
            guard let cellHeight = delegate?.collectionView(collectionView, heightFor: indexPath) else { continue }
            
            // 3. 消除上下空白:垂直方向无额外间距,Item直接衔接
            let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: cellHeight)
            // 若需要保留极小垂直间距,取消下面注释并调整cellPadding值:
            // let cellVerticalPadding: CGFloat = 2
            // let height = cellVerticalPadding * 2 + cellHeight
            // let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
            // let insetFrame = frame.insetBy(dx: cellHorizontalPadding, dy: cellVerticalPadding)
            
            // 4. 创建并缓存布局属性
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            attributes.frame = frame // 若保留垂直间距则改为insetFrame
            cache.append(attributes)
            
            // 5. 更新列的垂直偏移和整体内容高度
            contentHeight = max(contentHeight, frame.maxY)
            yOffset[column] = frame.maxY // 无额外间距,直接衔接下一个Item
            
            // 切换到下一列
            column = column < (numberOfColumns - 1) ? (column + 1) : 0
        }
    }
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()
        for attributes in cache {
            if attributes.frame.intersects(rect) {
                visibleLayoutAttributes.append(attributes)
            }
        }
        return visibleLayoutAttributes
    }
    
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return cache[indexPath.item]
    }
}

protocol DynamicLayoutDelegate: class {
    func collectionView(_ collectionView: UICollectionView, heightFor indexPath: IndexPath) -> CGFloat
}

关键调整点说明

  1. 解决布局水平居中问题

    • 自动计算列宽(或固定列宽时计算左右内边距),让所有列整体居中显示在CollectionView内,消除左右侧的空白。
    • 优化xOffset计算逻辑,保证列与列之间的水平间距均匀一致。
  2. 消除Item上下空白

    • 将原有的通用cellPadding拆分为仅作用于水平方向的cellHorizontalPadding,垂直方向直接让Item上下衔接,彻底消除不必要的空白。
    • 若需要保留极小的垂直间距,可取消代码中相关注释并调整垂直padding值。
  3. 修复内存泄漏风险

    • delegate改为weak引用,避免CollectionView与布局之间形成循环引用导致内存泄漏。
  4. 增强代码健壮性

    • 增加guard let处理cellHeight的可选值,避免因代理方法未实现导致的崩溃。
    • 移除了原代码中冗余的xOffset变量定义,避免变量冲突。

如果你的需求是让同一行的不同列Item垂直居中对齐(而非瀑布流的顶部对齐),可以告诉我,我再帮你调整布局逻辑~

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

火山引擎 最新活动