使用UITableViewAutomaticDimension时内嵌UICollectionView的UITableView高度计算问题
我之前也踩过一模一样的坑!这种情况本质是UITableView计算行高时,内嵌的UICollectionView还没完成布局,导致高度计算不准,再加上系统自带的UIView-Encapsulated-Layout-Height约束冲突,就会出现你说的滚动后才修正、复用单元格高度异常的问题。下面给你几个经过实际项目验证的解决方案:
1. 强制UICollectionView提前完成布局,让UITableView拿到准确高度
UITableView的自动行高计算会在单元格布局完成前就执行,这时候UICollectionView的contentSize还没更新,导致行高计算错误。我们可以在UITableViewCell的layoutSubviews里强制刷新UICollectionView的布局,再同步更新它的高度约束:
override func layoutSubviews() { super.layoutSubviews() // 强制collectionView完成当前布局 collectionView.layoutIfNeeded() // 更新高度约束为collectionView实际内容高度 collectionViewHeightConstraint.constant = collectionView.contentSize.height }
另外,在你从服务器拿到图标数据后,要先给UICollectionView赋值数据源并刷新,再让单元格强制布局,最后刷新UITableView的对应行:
// 假设在tableView的cellForRowAt方法中 cell.iconData = fetchedIconData cell.collectionView.reloadData() // 强制单元格完成布局,确保collectionView高度更新 cell.layoutIfNeeded() // 刷新当前行,让tableView重新计算行高 tableView.reloadRows(at: [indexPath], with: .none)
2. 解决UIView-Encapsulated-Layout-Height约束冲突
这个冲突是因为UITableView的自动行高机制会生成一个优先级很高的内置高度约束,把你手动添加的UICollectionView高度约束挤掉。解决方法很简单:降低自定义高度约束的优先级,让它和系统约束和平共处:
// 在初始化单元格或设置约束时,把高度约束优先级设为999(系统默认是1000) collectionViewHeightConstraint.priority = UILayoutPriority(999)
同时建议给单元格的contentView设置自适应属性,进一步避免布局冲突:
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
3. 提前计算UICollectionView高度(不依赖布局)
既然你提到UICollectionView自身尺寸已知,那我们完全可以绕过布局,直接通过数据量计算出准确高度,这样就从根源上避免了行高计算延迟的问题:
// 示例:假设每行显示3个图标,每个图标宽高80,间距10 func calculateCollectionViewHeight(itemCount: Int) -> CGFloat { let itemsPerRow = 3 let itemSize = CGSize(width: 80, height: 80) let spacing: CGFloat = 10 // 计算需要多少行 let rows = ceil(CGFloat(itemCount) / CGFloat(itemsPerRow)) // 总高度 = 行高总和 + 行间距总和 return rows * itemSize.height + (rows - 1) * spacing }
然后在UITableView的heightForRowAt方法里直接返回这个计算值即可,这种方法稳定性极高,适合布局规则固定的场景。
4. 清空UITableView的行高缓存
有时候行高异常是因为UITableView的缓存机制保留了旧的行高数据,我们可以扩展UITableView来清空缓存:
extension UITableView { func clearRowHeightCache() { if #available(iOS 15.0, *) { rowHeightCache.removeAllObjects() } else { // 低版本通过重置行高再刷新来清空缓存 rowHeight = UITableView.automaticDimension reloadData() } } }
在你拿到服务器数据后,调用tableView.clearRowHeightCache()再刷新表格,就能避免复用旧的错误行高。
最推荐的组合方案是方法1+方法2:既保证UICollectionView布局完成后再计算行高,又解决了约束冲突的问题,基本能覆盖大部分场景。
内容的提问来源于stack exchange,提问作者Makalele

