Swift中如何不重载TableView实现单元格内动态CollectionView
不重载TableView实现内嵌动态CollectionView无卡顿更新方案
调用tableView.reloadData()卡顿的核心原因是该方法会全量重载所有可见Cell、重算所有行高度,这类操作完全没必要,按以下逻辑改造即可实现流畅更新:
- 把高度更新逻辑从
cellForRowAt中移除,不要在Cell出列复用的回调里主动修改CollectionView高度约束,避免和TableView的布局周期冲突。 - 给内嵌CollectionView的自定义TableViewCell添加contentSize监听,在Cell内部捕获CollectionView的高度变化,通过回调向外层传递高度变更信号,核心实现代码如下:
class EmbeddedCollectionTableViewCell: UITableViewCell { @IBOutlet weak var innerCollectionView: UICollectionView! @IBOutlet weak var collectionHeightConstraint: NSLayoutConstraint! var heightUpdateHandler: ((CGFloat) -> Void)? private var dataList: [Any] = [] override func awakeFromNib() { super.awakeFromNib() innerCollectionView.dataSource = self // KVO监听CollectionView内容尺寸变化 innerCollectionView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil) } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard keyPath == "contentSize", let newSize = change?[.newKey] as? CGSize else { return } // 高度差小于0.1不更新,避免重复触发布局 guard abs(collectionHeightConstraint.constant - newSize.height) > 0.1 else { return } collectionHeightConstraint.constant = newSize.height heightUpdateHandler?(newSize.height) } // 对外暴露的更新数据方法 func configWithData(_ items: [Any]) { dataList = items innerCollectionView.reloadData() } deinit { innerCollectionView.removeObserver(self, forKeyPath: "contentSize") } } extension EmbeddedCollectionTableViewCell: UICollectionViewDataSource { // 实现CollectionView对应数据源方法即可 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return dataList.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "InnerItemCell", for: indexPath) // 配置CollectionView Cell内容 return cell } }
- 外层TableView只做单行高度重算,不重载任何Cell,从根源避免卡顿。不需要实现
heightForRowAt方法,直接在初始化时打开自适应高度,在cellForRowAt中绑定高度回调即可:
// 比如在viewDidLoad中做基础配置 override func viewDidLoad() { super.viewDidLoad() tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 200 // 填入和你实际Cell高度接近的估算值,减少高度计算耗时 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "EmbeddedCollectionTableViewCell", for: indexPath) as! EmbeddedCollectionTableViewCell // 传入对应行的Collection数据 cell.configWithData(sectionDataList[indexPath.row].collectionItems) // 绑定高度更新回调 cell.heightUpdateHandler = { [weak tableView] _ in guard let tableView = tableView else { return } // 核心:仅触发布局更新重算行高,不会重载任何Cell,无卡顿 UIView.performWithoutAnimation { tableView.beginUpdates() tableView.endUpdates() } } return cell }
- CollectionView加载更多数据时,不需要调用
tableView.reloadData(),直接更新对应数据源,给可见的目标Cell传入新数据即可,不可见的Cell会在下次复用时自动加载最新数据:
// 假设第0行的Collection加载到了更多数据 let targetRow = IndexPath(row: 0, section: 0) sectionDataList[targetRow.row].collectionItems.append(contentsOf: newLoadItems) // 仅更新当前可见的目标Cell if let targetCell = tableView.cellForRow(at: targetRow) as? EmbeddedCollectionTableViewCell { targetCell.configWithData(sectionDataList[targetRow.row].collectionItems) }
注意:不要在调用
collectionView.reloadData()后立即读取collectionViewContentSize修改约束,此时CollectionView还未完成布局,拿到的是旧值,会出现高度计算错误。用KVO监听contentSize的方式可以覆盖首次加载、加载更多、屏幕旋转等所有触发高度变化的场景,稳定性更高。
内容的提问来源于stack exchange,提问作者güney
相关产品推荐
相关产品推荐

