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

