如何使用UIKit的UICollectionView实现图片中的网格布局?
用UICollectionView实现自定义网格布局方案
要实现你需要的不规则网格布局,核心是自定义UICollectionViewLayout替代系统默认流式布局,以下是完整实现步骤:
1. 定义布局数据模型
创建模型类缓存每个单元格的布局信息:
struct GridItemLayoutInfo { let indexPath: IndexPath let frame: CGRect let size: CGSize }
2. 自定义UICollectionViewLayout子类
创建CustomGridLayout类,重写关键方法计算单元格位置与尺寸:
class CustomGridLayout: UICollectionViewLayout { private var layoutInfos: [GridItemLayoutInfo] = [] private let cellSpacing: CGFloat = 8 // 单元格间距 private let columnCount: Int = 2 // 列数 private var contentWidth: CGFloat { guard let collectionView = collectionView else { return 0 } let insets = collectionView.contentInset return collectionView.bounds.width - (insets.left + insets.right) } private var columnWidth: CGFloat { return (contentWidth - cellSpacing * CGFloat(columnCount - 1)) / CGFloat(columnCount) } override func prepare() { super.prepare() guard let collectionView = collectionView else { return } layoutInfos.removeAll() var yOffsets: [CGFloat] = Array(repeating: 0, count: columnCount) let itemCount = collectionView.numberOfItems(inSection: 0) for itemIndex in 0..<itemCount { let indexPath = IndexPath(item: itemIndex, section: 0) // 可根据你的布局规则调整大单元格判断逻辑 let isLargeItem = itemIndex % 4 == 0 let column = findShortestColumn(yOffsets: yOffsets) var itemSize: CGSize if isLargeItem { // 处理占2列2行的大单元格,避免越界 if column == columnCount - 1 { itemSize = CGSize(width: columnWidth, height: columnWidth) } else { itemSize = CGSize(width: columnWidth * 2 + cellSpacing, height: columnWidth * 2 + cellSpacing) yOffsets[column] += itemSize.height + cellSpacing yOffsets[column + 1] = yOffsets[column] let frame = CGRect(x: CGFloat(column) * (columnWidth + cellSpacing), y: yOffsets[column] - itemSize.height - cellSpacing, width: itemSize.width, height: itemSize.height) layoutInfos.append(GridItemLayoutInfo(indexPath: indexPath, frame: frame, size: itemSize)) continue } } else { itemSize = CGSize(width: columnWidth, height: columnWidth) } let x = CGFloat(column) * (columnWidth + cellSpacing) let y = yOffsets[column] let frame = CGRect(x: x, y: y, width: itemSize.width, height: itemSize.height) layoutInfos.append(GridItemLayoutInfo(indexPath: indexPath, frame: frame, size: itemSize)) yOffsets[column] += itemSize.height + cellSpacing } } private func findShortestColumn(yOffsets: [CGFloat]) -> Int { var minOffset = yOffsets[0] var column = 0 for (index, offset) in yOffsets.enumerated() { if offset < minOffset { minOffset = offset column = index } } return column } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { return layoutInfos.compactMap { info in let attributes = UICollectionViewLayoutAttributes(forCellWith: info.indexPath) attributes.frame = info.frame return attributes }.filter { $0.frame.intersects(rect) } } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { guard let info = layoutInfos.first(where: { $0.indexPath == indexPath }) else { return nil } let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) attributes.frame = info.frame return attributes } override var collectionViewContentSize: CGSize { guard let collectionView = collectionView else { return .zero } let insets = collectionView.contentInset guard let maxY = layoutInfos.map({ $0.frame.maxY }).max() else { return .zero } return CGSize(width: contentWidth, height: maxY + insets.bottom) } }
3. 在ViewController中集成使用
初始化UICollectionView时绑定自定义布局:
class GridViewController: UIViewController, UICollectionViewDataSource { private var collectionView: UICollectionView! override func viewDidLoad() { super.viewDidLoad() setupCollectionView() } private func setupCollectionView() { let layout = CustomGridLayout() collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) collectionView.dataSource = self collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell") collectionView.contentInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) collectionView.backgroundColor = .white view.addSubview(collectionView) collectionView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ collectionView.topAnchor.constraint(equalTo: view.topAnchor), collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 20 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) let isLargeItem = indexPath.item % 4 == 0 cell.backgroundColor = isLargeItem ? .systemBlue : .systemGray return cell } }
4. 适配目标布局
你可以修改CustomGridLayout中isLargeItem的判断逻辑,比如按参考图的行列规律指定大单元格位置,或根据业务数据动态设置尺寸,只需确保处理好边界情况(如避免大单元格超出列数范围)。
内容的提问来源于stack exchange,提问作者user19726662
相关产品推荐
相关产品推荐

