UICollectionView删除单元格触发NSInternalInconsistencyException异常求助
这个错误是UICollectionView更新时最常见的坑——你只执行了UI层面的删除操作,但没有同步更新对应的数据源。
为什么会抛出这个异常?
错误信息已经说得很清楚了:更新前section 0有4个item,你告诉CollectionView要删除1个,那更新后应该是3个,但实际数据源返回的数量还是4,导致数据和UI状态不匹配,系统就抛出了NSInternalInconsistencyException。
对比你的插入操作,你在插入时肯定同步更新了numberOfPages(比如numberOfPages += 1),所以数据源和UI操作是匹配的,插入才能正常执行。但删除时你漏掉了修改数据源这关键一步。
具体解决步骤
你需要严格遵循「先更新数据源,再执行UI更新」的顺序:
第一步:修改数据源
如果你是用数组存储页面数据,先从数组中移除对应元素;如果是用numberOfPages这类计数变量,先把计数减1。
比如:// 假设你的数据源是一个数组 yourDataSourceArray.remove(at: indexNumber) // 或者如果用计数变量: // self.numberOfPages -= 1第二步:执行UI删除操作
这时候再调用deleteItems(at:),此时数据源数量和UI操作的预期已经匹配,就不会抛出异常了。第三步:修复滚动逻辑
你原来的scrollToItem会有问题——因为删除后deletingIndexPath对应的item已经不存在了(后面的item会前移填补位置)。需要根据删除的位置调整滚动目标:- 如果删除的是最后一个item,滚动到新的最后一个位置(
indexNumber - 1) - 如果删除的不是最后一个,可以滚动到原来的
indexNumber位置(因为后面的item已经前移到这个位置了) - 还要注意边界情况,比如删除第一个item时,滚动到0位置
- 如果删除的是最后一个item,滚动到新的最后一个位置(
修正后的完整代码示例
// 1. 先更新数据源 yourDataSourceArray.remove(at: indexNumber) // self.numberOfPages -= 1 // 如果用计数变量的话 // 2. 执行删除 let deletingIndexPath = IndexPath(item: indexNumber, section: 0) self.collectionViewPager.deleteItems(at: [deletingIndexPath]) // 3. 处理滚动目标 let targetItemIndex: Int if indexNumber == yourDataSourceArray.count { // 删除的是最后一个,滚动到新的最后一个 targetItemIndex = indexNumber - 1 } else { // 否则滚动到原来的位置(后面的item已经前移了) targetItemIndex = indexNumber } // 避免索引越界(比如删除第一个item时,targetItemIndex会是0) let safeTargetIndex = max(0, targetItemIndex) let targetIndexPath = IndexPath(item: safeTargetIndex, section: 0) self.collectionViewPager.scrollToItem(at: targetIndexPath, at: UICollectionViewScrollPosition(rawValue: 0), animated: true)
关键提醒
UICollectionView(包括UITableView)的所有增删改操作,都必须保证数据源的状态和UI操作完全同步。系统会在执行UI更新时,通过numberOfItems(inSection:)等数据源方法校验数量是否匹配,不匹配就会抛出异常。
内容的提问来源于stack exchange,提问作者Asif Raza

