UICollectionView referenceSizeForHeaderInSection引发崩溃求助
解决UICollectionView批量更新时的崩溃问题
你遇到的NSInvalidArgumentException崩溃,根源在于滥用reloadData,尤其是在performBatchUpdates的操作块和完成回调中重复调用它,导致UICollectionView布局系统处理header尺寸时出现状态不一致,触发了"key cannot be nil"的异常。下面是具体的修复方案:
核心问题分析
- 你在批量更新(插入、删除cell)时,多次调用
reloadData,这会打乱UICollectionView的内部布局状态,和performBatchUpdates的原子性操作冲突。 - 在
performBatchUpdates的完成回调中调用reloadData,会让布局系统在未完成当前更新流程时再次刷新,导致内部字典的键值出现nil的情况。 - 你的
referenceSizeForHeaderInSection实现本身是正确的,但需要确保数据源和UI操作严格同步,否则布局计算会出现异常。
具体修复步骤
1. 移除所有不必要的reloadData调用
删除以下场景中的reloadData:
observeNewMatches的performBatchUpdates块内的两次reloadData和无效的numberOfItems(inSection: 0)调用manageExistMatch完成回调中的reloadDatadeleteMatch完成回调中的reloadData
2. 修正批量更新的操作顺序
遵循UICollectionView批量更新的核心原则:先修改数据源,再执行对应的UI操作,确保数据源和UI状态完全同步。
修复后的关键代码片段
修正后的新匹配观察者
MatchObserverManager.shared.observeNewMatches(self, isObserve: true, completion: { [weak self] (match) in DispatchQueue.main.async { guard let self = self else { return } self.collectionView.performBatchUpdates({ // 先更新数据源 self.newInactiveMatches.insert(match, at: 0) // 再通知collectionView插入对应item let indexPath = IndexPath(row: 0, section: 0) self.collectionView.insertItems(at: [indexPath]) }, completion: nil) } }) { (error) in DispatchQueue.main.async { guard !HUD.isVisible() else { return } HUD.showError(error.localizedDescription) } }
修正后的已激活匹配处理
private func manageExistMatch(_ match: MatchPersonalModel) { if let index = newInactiveMatches.index(where: { $0.matchID == match.matchID }), match.isActive { collectionView.performBatchUpdates({ // 先更新数据源 self.newInactiveMatches.remove(at: index) let indexPath = IndexPath(row: index, section: 0) self.collectionView.deleteItems(at: [indexPath]) self.activeMatches.insert(match, at: 0) let activeIndexPath = IndexPath(row: 0, section: 1) self.collectionView.insertItems(at: [activeIndexPath]) }, completion: nil) return } guard let activeIndex = activeMatches.index(where: { $0.matchID == match.matchID }) else { return } activeMatches[activeIndex] = match let indexPath = IndexPath(row: activeIndex, section: 1) collectionView.performBatchUpdates({ self.collectionView.reloadItems(at: [indexPath]) }, completion: nil) }
修正后的匹配删除逻辑
private func deleteMatch(_ index: Int, isNewMatch: Bool) { var sectionIndex = 1 // 默认是已激活匹配 if isNewMatch { sectionIndex = 0 } let indexPath = IndexPath(row: index, section: sectionIndex) // 先更新数据源 if isNewMatch { newInactiveMatches.remove(at: index) } else { activeMatches.remove(at: index) } collectionView.performBatchUpdates({ self.collectionView.deleteItems(at: [indexPath]) }, completion: nil) }
修复原理说明
当你严格按照"先改数据源,再执行UI操作"的逻辑进行批量更新时:
- UICollectionView会自动处理header的显示/隐藏,因为
referenceSizeForHeaderInSection会根据最新的数据源count返回正确的尺寸。 - 避免了
reloadData带来的全局刷新,保证了批量更新的原子性和布局系统的稳定性,从根本上解决了"key cannot be nil"的崩溃问题。
内容的提问来源于stack exchange,提问作者Alexander Khitev
相关产品推荐
相关产品推荐

