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

