UICollectionViewDiffableDataSourceReference更新后Cell未显示问题求助
看起来你遇到的问题是UICollectionViewDiffableDataSourceReference在Core Data对象更新后,虽然cellProvider返回了新的Cell实例,但CollectionView并未正确展示它,界面上仍保留旧Cell。结合你的日志和代码,我判断这大概率是Objective-C兼容的Reference版本Diffable Data Source的特性限制,加上Core Data更新时Snapshot处理的小细节导致的。下面是具体的修复方案:
1. 替换为Swift泛型版本的Diffable Data Source
UICollectionViewDiffableDataSourceReference是为Objective-C兼容设计的,在Swift中使用它容易出现一些奇怪的适配问题,尤其是和Core Data的NSManagedObjectID结合时。推荐使用原生Swift泛型版本UICollectionViewDiffableDataSource:
步骤1:定义Section类型
首先添加一个Section枚举(如果只有一个分区,也可以用String或Int):
enum CollectionSection: CaseIterable { case main }
步骤2:修改Diffable Data Source声明
替换原来的UICollectionViewDiffableDataSourceReference为泛型版本:
var diffableDataSource: UICollectionViewDiffableDataSource<CollectionSection, NSManagedObjectID>!
步骤3:重构CellProvider初始化
在CollectionView的init方法里,初始化泛型版本的数据源:
diffableDataSource = UICollectionViewDiffableDataSource<CollectionSection, NSManagedObjectID>(collectionView: self) { [weak self] collectionView, indexPath, objectID in guard let self = self else { return nil } let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCell.identifier, for: indexPath) as! MyCell let test = AppDelegate.persistentContainer.viewContext.object(with: objectID) as! Test cell.update(test: test) print("Returning dequeued => \(cell), test test.a => \(test.a!)") return cell }
步骤4:适配Snapshot回调
修改NSFetchedResultsController的回调方法,将Reference类型的Snapshot转换为泛型版本:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { guard let typedSnapshot = snapshot as? NSDiffableDataSourceSnapshot<CollectionSection, NSManagedObjectID> else { // 如果转换失败,手动生成Snapshot var manualSnapshot = NSDiffableDataSourceSnapshot<CollectionSection, NSManagedObjectID>() manualSnapshot.appendSections([.main]) let objectIDs = fetchResultController.fetchedObjects?.map { $0.objectID } ?? [] manualSnapshot.appendItems(objectIDs) diffableDataSource.apply(manualSnapshot, animatingDifferences: true) return } diffableDataSource.apply(typedSnapshot, animatingDifferences: true) }
2. 修复Cell复用的隐藏状态问题
从你的日志可以看到,旧Cell在更新前被标记为hidden = YES,这可能是CollectionView复用机制导致的残留状态。在Cell的update方法里强制取消隐藏:
func update(test: Test) { self.test = test self.isHidden = false // 确保Cell始终可见 print("update(test:) -> \(self)") label.text = test.a }
3. 确保Core Data上下文自动合并
检查你的AppDelegate中,PersistentContainer的ViewContext是否开启了自动合并后台上下文的变化,这是NSFetchedResultsController能及时收到更新通知的前提:
lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "CollectionViewBug") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) // 关键:自动合并后台上下文的修改 container.viewContext.automaticallyMergesChangesFromParent = true container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy return container }()
4. 优化Core Data更新的错误处理
避免代码中的try!强制解包,添加错误处理防止崩溃:
@objc func doAction() { guard let testID = test?.objectID else { return } AppDelegate.persistentContainer.performBackgroundTask { ctx in do { let test = ctx.object(with: testID) as! Test test.a = UUID().uuidString try ctx.save() } catch { print("Failed to save test object: \(error.localizedDescription)") } } }
这些修改应该能解决你遇到的Cell不更新问题。核心原因是Reference版本的Diffable Data Source在处理Core Data对象更新时,对Snapshot的状态判断存在兼容问题,换成Swift原生泛型版本后能更准确地识别Item的变化,确保CollectionView正确刷新Cell。
内容的提问来源于stack exchange,提问作者netdigger

