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

