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

如何在UICollectionView自定义布局中根据图片高度计算Cell高度

解决Pinterest风格UICollectionView根据图片高度动态设置Cell高度的问题

搞定这个需求的核心是先获取图片原始尺寸,再根据UICollectionView的列宽按比例计算Cell高度,同时配合Kingfisher的加载回调来更新布局。下面是一步步的实现方案:

1. 准备数据模型缓存高度

首先创建一个模型类,用来存储图片URL和计算好的Cell高度,避免重复计算:

struct ImageItem {
    let imageURL: URL
    var cellHeight: CGFloat = 0 // 缓存计算后的Cell高度,默认0表示未计算
}

2. 给自定义Pinterest布局添加代理

在你参考的Pinterest自定义布局类中,添加一个代理协议,用来获取每个Item的高度:

protocol PinterestLayoutDelegate: AnyObject {
    func collectionView(_ collectionView: UICollectionView, heightForItemAt indexPath: IndexPath) -> CGFloat
}

class PinterestLayout: UICollectionViewLayout {
    weak var delegate: PinterestLayoutDelegate?
    // 保留原有布局代码(比如列数、间距、布局属性缓存等)
    
    override func prepare() {
        super.prepare()
        guard let collectionView = collectionView else { return }
        let itemCount = collectionView.numberOfItems(inSection: 0)
        
        // 遍历每个Item,通过代理获取高度并计算布局
        for item in 0..<itemCount {
            let indexPath = IndexPath(item: item, section: 0)
            // 从代理拿到高度,默认给个占位高度(比如200)
            let itemHeight = delegate?.collectionView(collectionView, heightForItemAt: indexPath) ?? 200
            
            // 这里替换原有布局代码中固定高度的部分,用itemHeight来计算Item的frame
            // (参考原教程中的frame计算逻辑,把高度替换为itemHeight即可)
        }
    }
}

3. 在ViewController中实现代理与Kingfisher加载逻辑

让你的ViewController遵循PinterestLayoutDelegate,并在cellForItemAt中用Kingfisher加载图片、获取尺寸并更新布局:

class YourViewController: UIViewController, UICollectionViewDataSource, PinterestLayoutDelegate {
    var imageItems: [ImageItem] = []
    @IBOutlet weak var collectionView: UICollectionView!
    // 引用自定义布局,方便获取列数和间距
    private var pinterestLayout: PinterestLayout {
        return collectionView.collectionViewLayout as! PinterestLayout
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 设置布局代理
        pinterestLayout.delegate = self
        // 加载你的图片数据
        loadImageData()
    }
    
    // MARK: - PinterestLayoutDelegate
    func collectionView(_ collectionView: UICollectionView, heightForItemAt indexPath: IndexPath) -> CGFloat {
        // 返回缓存的Cell高度
        return imageItems[indexPath.item].cellHeight
    }
    
    // MARK: - UICollectionViewDataSource
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return imageItems.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DbatCollectionViewCell", for: indexPath) as! DbatCollectionViewCell
        let currentItem = imageItems[indexPath.item]
        
        // 用Kingfisher加载图片,同时获取图片尺寸
        cell.imageView.kf.setImage(with: currentItem.imageURL) { [weak self] result in
            guard let self = self, case .success(let imageResult) = result else {
                // 加载失败可以设置占位图高度
                self?.imageItems[indexPath.item].cellHeight = 200
                self?.collectionView.reloadItems(at: [indexPath])
                return
            }
            
            // 获取图片原始尺寸
            let originalSize = imageResult.image.size
            // 计算列宽:总宽度减去间距,再平分到每一列
            let columnWidth = (self.collectionView.frame.width - (self.pinterestLayout.columnCount - 1) * self.pinterestLayout.cellSpacing) / CGFloat(self.pinterestLayout.columnCount)
            // 按比例计算Cell高度
            let scale = columnWidth / originalSize.width
            let calculatedHeight = originalSize.height * scale
            
            // 更新模型中的高度缓存
            self.imageItems[indexPath.item].cellHeight = calculatedHeight
            
            // 刷新当前Item的布局,让高度生效
            self.collectionView.performBatchUpdates({
                self.collectionView.reloadItems(at: [indexPath])
            }, completion: nil)
        }
        
        // 如果还没计算出高度,先显示占位图
        if currentItem.cellHeight == 0 {
            cell.imageView.image = UIImage(named: "placeholder_image")
        }
        
        return cell
    }
    
    // 示例:加载图片数据
    private func loadImageData() {
        // 替换成你的实际图片URL数组
        let sampleURLs = [
            URL(string: "https://your-image-url-1.jpg")!,
            URL(string: "https://your-image-url-2.jpg")!
        ]
        imageItems = sampleURLs.map { ImageItem(imageURL: $0) }
        collectionView.reloadData()
    }
}

4. 优化:提前预加载图片尺寸

如果不想在滚动时才逐个计算高度,可以在页面初始化时批量预加载所有图片的尺寸,这样打开页面就能直接显示正确高度的Cell:

private func preloadAllImageSizes() {
    for (index, item) in imageItems.enumerated() {
        // 只加载图片的第一帧,加快速度
        KingfisherManager.shared.retrieveImage(with: item.imageURL, options: [.onlyLoadFirstFrame]) { [weak self] result in
            guard let self = self, case .success(let imageResult) = result else { return }
            
            let originalSize = imageResult.image.size
            let columnWidth = (self.collectionView.frame.width - (self.pinterestLayout.columnCount - 1) * self.pinterestLayout.cellSpacing) / CGFloat(self.pinterestLayout.columnCount)
            let scale = columnWidth / originalSize.width
            self.imageItems[index].cellHeight = originalSize.height * scale
            
            // 全部预加载完成后刷新布局
            if index == self.imageItems.count - 1 {
                self.collectionView.reloadData()
            }
        }
    }
}

你可以在loadImageData之后调用这个方法。

这样一来,你的Pinterest风格布局就能根据每张图片的实际高度动态调整Cell的高度,完美适配需求啦!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:39:49