求助:TableViewCell内嵌UICollectionView切换时出现NSInternalInconsistencyException崩溃
我之前也踩过这个一模一样的坑,这种崩溃的核心原因几乎都是UICollectionView的布局缓存和当前数据源不同步——当你点击按钮切换CollectionView(不管是切换它的数据源、布局,还是直接替换CollectionView实例)时,旧的布局系统还持有之前的索引路径属性,但新的数据源里已经没有对应的条目了,就会抛出这个异常。
给你几个亲测有效的解决思路:
先清空布局缓存再刷新数据:在切换数据源或者更新布局之前,一定要先调用
collectionView.collectionViewLayout.invalidateLayout(),强制布局系统丢弃所有缓存的布局属性,然后再调用collectionView.reloadData()。如果是替换整个CollectionView实例,记得在移除旧实例前,把它的dataSource和delegate置为nil,避免旧的引用还在后台发送布局请求。严格保证数据源更新和UI刷新的顺序:永远先更新你的数据源数组,再触发CollectionView的刷新操作,绝对不能反过来。比如:
// 正确顺序 self.collectionData = newDataArray self.collectionView.reloadData() // 错误!不要这么写 self.collectionView.reloadData() self.collectionData = newDataArray如果是做批量增删操作,要用
performBatchUpdates(_:completion:)包裹,但如果是完全切换数据源,直接调用reloadData反而更稳妥。检查自定义布局的缓存逻辑:如果你用了自定义的
UICollectionViewLayout,去看看prepare()方法里是不是缓存了布局属性数组,但没有在布局失效时清空这些缓存。这种情况下,需要重写invalidateLayout()方法,在里面清空所有缓存的属性,确保下次布局时重新计算所有条目:class CustomCollectionLayout: UICollectionViewLayout { private var cachedAttributes: [UICollectionViewLayoutAttributes] = [] override func invalidateLayout() { super.invalidateLayout() cachedAttributes.removeAll() // 清空缓存 } override func prepare() { super.prepare() // 重新计算并填充cachedAttributes } }处理TableViewCell复用带来的状态残留:因为CollectionView嵌在TableViewCell里,当Cell被复用时,旧的CollectionView状态可能没有被完全重置。所以在TableView的
cellForRowAt方法中配置Cell时,一定要先重置CollectionView的数据源,再刷新,同时确保Cell复用时,CollectionView的dataSource和delegate都正确关联到当前Cell的逻辑:func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "EmbedCollectionCell", for: indexPath) as! EmbedCollectionCell // 先重置数据源 cell.collectionData = dataForRow(indexPath.row) // 清空布局缓存并刷新 cell.collectionView.collectionViewLayout.invalidateLayout() cell.collectionView.reloadData() return cell }
如果上面的方法都试过还是不行,可以试试“暴力重置”:在切换的时候,先把旧的CollectionView从Cell的视图层级中移除,然后重新初始化一个新的CollectionView实例,设置好布局、数据源和delegate后再添加到Cell里。这种方法虽然看起来笨,但有时候能解决一些顽固的缓存残留问题。
内容的提问来源于stack exchange,提问作者devang bhatt

