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

使用UICollectionViewFlowLayout.automaticSize实现瀑布流时无法自定义cell间距

你遇到的间距异常是系统原生UICollectionViewFlowLayout的固有特性导致的。这套布局的逻辑是同一行的所有cell会以当前行最高cell的高度作为整行高度,其余短cell底部对齐,所以短cell下方会自动填充空白,自然就出现了不符合预期的大间距,和你设置的minimumLineSpacing、minimumInteritemSpacing参数无关。原生FlowLayout本身就不支持Pinterest这类错落的瀑布流布局,想要实现需求必须替换为自定义瀑布流布局。

具体修改步骤

第一步:替换自定义瀑布流布局

你可以直接使用成熟的自定义瀑布流实现,核心逻辑是提前计算每个cell的高度,分别记录每一列的总高度,每次把新的cell放到高度更短的一列里,下面是核心实现示例:

class WaterfallLayout: UICollectionViewLayout {
    // 列数,Pinterest效果这里设为2即可
    var columnCount: Int = 2
    // 列间距
    var interitemSpacing: CGFloat = 5
    // 行间距
    var lineSpacing: CGFloat = 5
    // 整体内边距
    var sectionInset: UIEdgeInsets = .zero
    // 代理用来获取cell高度
    weak var delegate: WaterfallLayoutDelegate?
    
    // 存储所有cell的布局属性
    private var layoutAttributes: [UICollectionViewLayoutAttributes] = []
    // 存储每一列的当前总高度
    private var columnHeights: [CGFloat] = []
    // 内容总高度
    private var contentHeight: CGFloat = 0
    
    override var collectionViewContentSize: CGSize {
        return CGSize(width: collectionView?.bounds.width ?? 0, height: contentHeight)
    }
    
    override func prepare() {
        super.prepare()
        guard let collectionView = collectionView else { return }
        // 重置缓存数据
        layoutAttributes.removeAll()
        columnHeights.removeAll()
        (0..<columnCount).forEach { _ in columnHeights.append(sectionInset.top) }
        contentHeight = 0
        
        let totalWidth = collectionView.bounds.width - sectionInset.left - sectionInset.right
        let itemWidth = (totalWidth - CGFloat(columnCount - 1) * interitemSpacing) / CGFloat(columnCount)
        
        // 遍历计算所有cell的布局
        let itemCount = collectionView.numberOfItems(inSection: 0)
        for i in 0..<itemCount {
            let indexPath = IndexPath(item: i, section: 0)
            // 找到当前高度最短的列
            let minHeight = columnHeights.min() ?? 0
            let targetColumn = columnHeights.firstIndex(of: minHeight) ?? 0
            // 计算cell的x、y坐标
            let x = sectionInset.left + CGFloat(targetColumn) * (itemWidth + interitemSpacing)
            let y = minHeight + (minHeight == sectionInset.top ? 0 : lineSpacing)
            // 从代理获取当前cell的实际高度
            let itemHeight = delegate?.waterfallLayout(self, heightForItemAt: indexPath, itemWidth: itemWidth) ?? 100
            // 生成布局属性
            let attribute = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            attribute.frame = CGRect(x: x, y: y, width: itemWidth, height: itemHeight)
            layoutAttributes.append(attribute)
            // 更新对应列的总高度
            columnHeights[targetColumn] = y + itemHeight
        }
        // 更新整体内容高度
        contentHeight = (columnHeights.max() ?? 0) + sectionInset.bottom
    }
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        return layoutAttributes.filter { $0.frame.intersects(rect) }
    }
    
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return layoutAttributes[indexPath.item]
    }
}

// 定义代理协议用来获取cell高度
protocol WaterfallLayoutDelegate: AnyObject {
    func waterfallLayout(_ layout: WaterfallLayout, heightForItemAt indexPath: IndexPath, itemWidth: CGFloat) -> CGFloat
}

第二步:修改CollectionView初始化代码

把原来的UICollectionViewFlowLayout替换成上面的自定义瀑布流布局,同时删掉原来FlowLayout相关的自动尺寸配置:

func setupCollectionView() {
    // 替换为自定义瀑布流布局
    let layout = WaterfallLayout()
    layout.columnCount = 2
    layout.interitemSpacing = 5
    layout.lineSpacing = 5
    layout.delegate = self // 当前控制器要遵守WaterfallLayoutDelegate协议
    
    let width = view.frame.size.width * 0.9
    collectionViewParent = UICollectionView(frame: .zero, collectionViewLayout: layout)
    collectionViewParent.backgroundColor = .red
    view.addSubview(collectionViewParent)
    collectionViewParent.translatesAutoresizingMaskIntoConstraints = false
    collectionViewParent.dataSource = self
    collectionViewParent.delegate = self
    collectionViewParent.register(LoansCell.self, forCellWithReuseIdentifier: "cellLoan")
    collectionViewParent.register(FirstRowCollectionViewCell.self, forCellWithReuseIdentifier: "cellFirst")

    collectionViewParent.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
    collectionViewParent.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20).isActive = true
    collectionViewParent.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    collectionViewParent.widthAnchor.constraint(equalToConstant: width).isActive = true
}

第三步:实现布局代理,计算每个cell的高度

你当前的cell已经有完整的上下约束,可以直接用自动布局计算实际高度:

extension YourViewController: WaterfallLayoutDelegate {
    func waterfallLayout(_ layout: WaterfallLayout, heightForItemAt indexPath: IndexPath, itemWidth: CGFloat) -> CGFloat {
        // 创建临时cell填充对应数据
        let cell = LoansCell()
        cell.configure(with: yourDataSource[indexPath.item]) // 替换为你自己的数据源赋值方法
        // 强制布局计算高度
        cell.view.widthAnchor.constraint(equalToConstant: itemWidth).isActive = true
        cell.layoutIfNeeded()
        return cell.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
    }
}

第四步:修正Cell里的冗余约束

把你当前Cell里写死的view.widthAnchor硬编码约束删掉,cell宽度会由布局控制,避免出现约束冲突。

补充说明

如果你的App最低支持iOS13及以上,也可以用UICollectionViewCompositionalLayout实现瀑布流,不需要自定义布局,代码更简洁。上述自定义瀑布流布局兼容性更好,支持iOS10及以上版本。

内容的提问来源于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.29 00:36:05