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

如何使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 02:30:59