如何让代码等待Collection View数据重载完成后再执行?
嘿,我完全懂你遇到的问题——reloadData()其实是异步触发视图刷新的,它只会把“需要刷新视图”这个任务丢给主线程的运行循环,不会等视图真的填充完成才返回。所以你直接在它后面写的代码,会先于第一个Collection View的实际刷新执行,这就导致了预期外的异常。
下面给你几个靠谱的解决方案,你可以根据自己的场景选择:
方案1:用performBatchUpdates的完成回调(最稳妥的视图刷新监听)
这个方法会在批量更新操作完成后触发回调,我们可以用它来包裹reloadData,确保第一个Collection View刷新完成后再处理第二个:
// 设置第一个Collection View的代理和数据源 photoCollectionView.dataSource = self photoCollectionView.delegate = self // 用performBatchUpdates包裹刷新,监听完成事件 photoCollectionView.performBatchUpdates({ self.photoCollectionView.reloadData() }, completion: { [weak self] isFinished in guard let self = self, isFinished else { return } // 这里第一个Collection View已经完成刷新,现在处理第二个 self.secondCollectionView.dataSource = self self.secondCollectionView.delegate = self self.secondCollectionView.reloadData() })
方案2:利用主线程串行队列的特性(简单场景适用)
主线程的任务是串行执行的,reloadData只是标记视图需要刷新,实际的刷新工作会在下一次RunLoop循环完成。我们可以把第二个Collection View的刷新任务放到下一次主线程循环里,确保它在第一个的刷新之后执行:
// 先处理第一个Collection View photoCollectionView.dataSource = self photoCollectionView.delegate = self photoCollectionView.reloadData() // 把第二个的刷新放到下一次主线程循环 DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.secondCollectionView.dataSource = self self.secondCollectionView.delegate = self self.secondCollectionView.reloadData() }
方案3:监听最后一个Cell的显示(精准判断视图填充完成)
如果需要确保第一个Collection View的所有Cell都已经显示在屏幕上,你可以用UICollectionViewDelegate的didFinishDisplayingCell回调,判断最后一个Cell显示完成后再执行后续操作:
首先,确保你有记录第一个Collection View的数据源数量:
var photoDataSource: [YourPhotoModel] = [] // 你的数据源数组
然后实现代理方法:
func collectionView(_ collectionView: UICollectionView, didFinishDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { // 判断当前是第一个Collection View,且是最后一个Cell if collectionView == photoCollectionView, indexPath.item == photoDataSource.count - 1 { // 所有Cell都已显示,现在处理第二个Collection View secondCollectionView.dataSource = self secondCollectionView.delegate = self secondCollectionView.reloadData() } }
⚠️ 注意:如果你的第一个Collection View是分页加载或者需要滚动才能显示所有Cell,这个方法可能不会立刻触发,只有当用户滑到最后一个Cell时才会执行。
额外建议:从数据源层面控制顺序(更根本的解决方式)
如果你的第二个Collection View的内容依赖第一个的数据,那最好先确保第一个的数据源完全加载完成,再去加载第二个的数据源,而不是依赖视图的刷新:
// 1. 先加载第一个Collection View的数据源 func loadPhotoData(completion: @escaping ([YourPhotoModel]) -> Void) { // 这里是你的数据加载逻辑(网络请求/本地读取) let fetchedData = ... // 获取到的照片数据 completion(fetchedData) } // 2. 加载第二个Collection View的数据源 func loadSecondCollectionData(completion: @escaping ([YourSecondModel]) -> Void) { // 第二个数据的加载逻辑 let fetchedData = ... completion(fetchedData) } // 调用顺序 loadPhotoData { [weak self] photoData in guard let self = self else { return } self.photoDataSource = photoData self.photoCollectionView.dataSource = self self.photoCollectionView.delegate = self self.photoCollectionView.reloadData() // 第一个数据源准备好后,再加载第二个 self.loadSecondCollectionData { secondData in self.secondDataSource = secondData self.secondCollectionView.dataSource = self self.secondCollectionView.delegate = self self.secondCollectionView.reloadData() } }
这样不管视图刷新的时机如何,数据源是严格按顺序准备的,视图的刷新也会按预期触发,从根源上避免了异步刷新带来的问题。
内容的提问来源于stack exchange,提问作者itsfaraaz

