iOS15+滚动时嵌套UICollectionView的UITableViewCell消失报错求解
问题根因
该断言触发的核心原因是iOS 15.x版本新增了UITableView预取缓存的一致性校验,结合当前代码的实现缺陷触发:
[Assert] UITableView internal inconsistency: cell prefetched for IP(0,6) already stored for IP(0,5).
现有实现存在两个直接触发问题的错误:
- 所有cell内部嵌套的UICollectionView都将delegate、dataSource指向外层控制器,多个collectionView共享同一个代理对象,cell复用时状态完全错位
- 每次cell出队时都重复执行collectionView的cell注册、代理赋值、reload操作,破坏了UITableView复用池和预取缓存的索引映射关系,滚动时预取缓存记录的indexPath和实际返回的cell不匹配,就会触发断言,严重时系统直接丢弃对应位置的cell导致列表项消失。
该问题仅在开启预取的iOS 15+环境触发,iOS 13因为没有这套一致性校验逻辑所以不会报错。
解决方案
按优先级从高到低可选:
- 重构嵌套滚动视图的职责划分
不要在tableView(_:cellForRowAt:)方法中操作cell内部collectionView的注册、代理设置、reload逻辑。将collectionView的delegate、dataSource实现下沉到自定义TestTableViewCell内部,每个cell独立管理自己内部collectionView的状态、数据、滚动偏移,从根源避免复用状态错乱。
注意:collectionView的cell注册只需要在cell的awakeFromNib方法中执行一次,不要重复注册。同时重写cell的prepareForReuse方法,在复用前重置collectionView的偏移量、临时数据,避免上一个cell的状态残留。 - 临时兼容修复
如果暂时没有时间重构架构,直接在tableView初始化时关闭预取功能即可绕过校验:
该操作对常规列表的滑动流畅度影响极小,可以立刻消除断言和cell消失的问题。tableView.prefetchingEnabled = false - 修正reload调用时机
给cell增加专门的数据绑定方法,只有当传入的新数据和当前cell持有的数据不一致时,再触发collectionView的reload,避免无意义的reload打乱系统的复用缓存逻辑。
修正后代码参考
// TestTableViewCell 内部实现 class TestTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource { @IBOutlet weak var collectionView: UICollectionView! private var itemList: [Any] = [] override func awakeFromNib() { super.awakeFromNib() // 仅初始化时执行一次注册、代理设置 collectionView.register(UINib(nibName: "TestCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "TestCollectionViewCell") collectionView.delegate = self collectionView.dataSource = self } override func prepareForReuse() { super.prepareForReuse() // 复用前重置状态 itemList.removeAll() collectionView.setContentOffset(.zero, animated: false) collectionView.reloadData() } // 对外仅暴露数据绑定接口 func config(with items: [Any]) { itemList = items collectionView.reloadData() } // MARK: - CollectionView 代理/数据源实现放在cell内部 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return itemList.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TestCollectionViewCell", for: indexPath) as! TestCollectionViewCell // 配置collectionCell内容 return cell } } // 外层TableView数据源实现 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableViewCell", for: indexPath) as? TestTableViewCell else { fatalError("Cell dequeue failed") } // 仅做数据绑定,不操作cell内部collectionView的配置 cell.config(with: sectionData[indexPath.row]) return cell }
内容的提问来源于stack exchange,提问作者Samuel Hinchcliffe
相关产品推荐
相关产品推荐

