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

iOS Swift如何用UICollectionView实现多列高度自适应瀑布流布局

iOS Swift 多列自适应高度瀑布流实现方案

你之前使用CompositionalLayout得到等高布局的原因是:NSCollectionLayoutGroup.horizontal设置subitem count为2时,系统会强制组内所有item高度统一为组的高度,无法实现错落的瀑布流效果。
以下是两种可行的实现方案,均不需要手动计算item高度,支持2列以上布局,适配复杂结构的item:


方案一:使用成熟第三方库实现(推荐,兼容性好)

推荐使用业界常用的CHTCollectionViewWaterfallLayout,已适配多版本iOS,自动处理高度计算逻辑,仅需要你的cell通过AutoLayout完成内部约束即可。

实现步骤:

  • 集成依赖:通过CocoaPods集成pod 'CHTCollectionViewWaterfallLayout',也支持手动导入或SPM集成
  • 初始化布局并配置参数:
import CHTCollectionViewWaterfallLayout

let layout = CHTCollectionViewWaterfallLayout()
layout.columnCount = 3 // 可自定义任意列数
layout.minimumInteritemSpacing = 8 // 列与列之间的间距
layout.minimumColumnSpacing = 8 // 上下item之间的间距
layout.sectionInset = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
layout.estimatedItemHeight = 120 // 只需要给预估高度,实际高度由cell的AutoLayout自动计算
collectionView.collectionViewLayout = layout
  • Cell约束要求:cell内部所有子视图的上下约束必须完整,多行文本控件需要设置numberOfLines = 0,确保cell可以根据内容自动撑开高度。

方案二:自定义UICollectionViewLayout实现(无三方依赖)

如果不想引入第三方库,可以自定义布局类,核心逻辑是维护每一列的当前高度,每次将新的item放到高度最小的列下方,自动通过AutoLayout计算cell实际高度:

核心代码示例:

class WaterfallFlowLayout: UICollectionViewLayout {
    /// 列数,可自定义
    var columnCount: Int = 2
    /// 列间距
    var interitemSpacing: CGFloat = 8
    /// 行间距
    var lineSpacing: CGFloat = 8
    /// 分区内边距
    var sectionInset: UIEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    /// 预估item高度
    var estimatedItemHeight: CGFloat = 100
    
    /// 所有item的布局属性缓存
    private var layoutAttributesCache: [UICollectionViewLayoutAttributes] = []
    /// 每一列的当前最大高度
    private var columnHeightList: [CGFloat] = []
    
    override var collectionViewContentSize: CGSize {
        guard let collectionView = collectionView else { return .zero }
        let maxHeight = columnHeightList.max() ?? 0
        return CGSize(width: collectionView.bounds.width, height: maxHeight + sectionInset.bottom)
    }
    
    override func prepare() {
        super.prepare()
        guard let collectionView = collectionView,
              let dataSource = collectionView.dataSource else { return }
        let itemCount = dataSource.collectionView(collectionView, numberOfItemsInSection: 0)
        guard itemCount > 0 else { return }
        
        // 计算单个item的宽度
        let totalAvailableWidth = collectionView.bounds.width - sectionInset.left - sectionInset.right
        let totalSpacing = CGFloat(columnCount - 1) * interitemSpacing
        let itemWidth = (totalAvailableWidth - totalSpacing) / CGFloat(columnCount)
        
        // 重置列高度缓存
        columnHeightList = Array(repeating: sectionInset.top, count: columnCount)
        layoutAttributesCache.removeAll()
        
        for itemIndex in 0..<itemCount {
            let indexPath = IndexPath(item: itemIndex, section: 0)
            // 找到当前高度最小的列,将新item放到该列
            let minColumnHeight = columnHeightList.min() ?? 0
            let targetColumnIndex = columnHeightList.firstIndex(of: minColumnHeight) ?? 0
            
            // 计算item的x、y坐标
            let itemX = sectionInset.left + CGFloat(targetColumnIndex) * (itemWidth + interitemSpacing)
            let itemY = minColumnHeight + lineSpacing
            
            // 自动计算cell实际高度
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "你的Cell复用ID", for: indexPath) as! 你的Cell类
            cell.bindData(data: 你的数据源数组[itemIndex]) // 给cell赋值数据触发布局
            cell.layoutIfNeeded()
            let fitSize = cell.systemLayoutSizeFitting(CGSize(width: itemWidth, height: UIView.layoutFittingCompressedSize.height))
            let itemHeight = fitSize.height
            
            // 保存布局属性
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            attributes.frame = CGRect(x: itemX, y: itemY, width: itemWidth, height: itemHeight)
            layoutAttributesCache.append(attributes)
            
            // 更新当前列的最大高度
            columnHeightList[targetColumnIndex] = itemY + itemHeight
        }
    }
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        return layoutAttributesCache.filter { $0.frame.intersects(rect) }
    }
    
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return layoutAttributesCache[indexPath.item]
    }
}

使用方式:

直接将collectionView的布局设置为自定义的WaterfallFlowLayout即可,cell的约束要求和方案一一致。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 18:27:03