You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

UICollectionView referenceSizeForHeaderInSection引发崩溃求助

解决UICollectionView批量更新时的崩溃问题

你遇到的NSInvalidArgumentException崩溃,根源在于滥用reloadData,尤其是在performBatchUpdates的操作块和完成回调中重复调用它,导致UICollectionView布局系统处理header尺寸时出现状态不一致,触发了"key cannot be nil"的异常。下面是具体的修复方案:

核心问题分析

  1. 你在批量更新(插入、删除cell)时,多次调用reloadData,这会打乱UICollectionView的内部布局状态,和performBatchUpdates的原子性操作冲突。
  2. 在performBatchUpdates的完成回调中调用reloadData,会让布局系统在未完成当前更新流程时再次刷新,导致内部字典的键值出现nil的情况。
  3. 你的referenceSizeForHeaderInSection实现本身是正确的,但需要确保数据源和UI操作严格同步,否则布局计算会出现异常。

具体修复步骤

1. 移除所有不必要的reloadData调用

删除以下场景中的reloadData:

  • observeNewMatches的performBatchUpdates块内的两次reloadData和无效的numberOfItems(inSection: 0)调用
  • manageExistMatch完成回调中的reloadData
  • deleteMatch完成回调中的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操作"的逻辑进行批量更新时:

  1. UICollectionView会自动处理header的显示/隐藏,因为referenceSizeForHeaderInSection会根据最新的数据源count返回正确的尺寸。
  2. 避免了reloadData带来的全局刷新,保证了批量更新的原子性和布局系统的稳定性,从根本上解决了"key cannot be nil"的崩溃问题。

内容的提问来源于stack exchange,提问作者Alexander Khitev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:30:44