The sample app has a table view that is powered by UITableViewDiffableDataSource
that gets data from NSFetchedResultsController
. You can add letters of the alphabet to the table view by pressing the plus button. To implement data source, I used this article. The issue is that when I add new item to Core Data NSFetchedResultsController
feeds temporary IDs to the cell provider. And when I scroll down and cell provider has to reuse cells, it fails to fetch managed object with temporary ID. It does not, however, happen when the item is added to the area of the table view that is on the screen.
lazy var fetchedResultsController: NSFetchedResultsController<Item> = {
let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
let sort = NSSortDescriptor(key: #keyPath(Item.name), ascending: true)
fetchRequest.sortDescriptors = [sort]
let controller = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: moc,
sectionNameKeyPath: nil,
cacheName: nil
)
controller.delegate = self
return controller
}()
func configureDiffableDataSource() {
let diffableDataSource = UITableViewDiffableDataSource<Int, NSManagedObjectID>(tableView: tableView) { (tableView, indexPath, objectID) -> UITableViewCell? in
guard let object = try? self.moc.existingObject(with: objectID) as? Item else {
// Crash happens here.
fatalError("Managed object should be available.")
}
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_id", for: indexPath)
cell.textLabel?.text = object.name
return cell
}
self.diffableDataSource = diffableDataSource
tableView.dataSource = diffableDataSource
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
guard let dataSource = tableView?.dataSource as? UITableViewDiffableDataSource<Int, NSManagedObjectID> else {
assertionFailure("The data source has not implemented snapshot support while it should.")
return
}
var snapshot = snapshot as NSDiffableDataSourceSnapshot<Int, NSManagedObjectID>
let currentSnapshot = dataSource.snapshot() as NSDiffableDataSourceSnapshot<Int, NSManagedObjectID>
let reloadIdentifiers: [NSManagedObjectID] = snapshot.itemIdentifiers.compactMap { itemIdentifier in
guard let currentIndex = currentSnapshot.indexOfItem(itemIdentifier), let index = snapshot.indexOfItem(itemIdentifier), index == currentIndex else {
return nil
}
guard let existingObject = try? controller.managedObjectContext.existingObject(with: itemIdentifier), existingObject.isUpdated else { return nil }
return itemIdentifier
}
snapshot.reloadItems(reloadIdentifiers)
let shouldAnimate = tableView?.numberOfSections != 0
dataSource.apply(snapshot as NSDiffableDataSourceSnapshot<Int, NSManagedObjectID>, animatingDifferences: shouldAnimate)
}
Adding try! fetchedResultsController.performFetch()
right after saving to Core Data fixes the issue, however, it’s a brute-force solution, which causes double call to controller(_:didChangeContentWith:)
delegate method and, sometimes, double animation. Fetching should happen automatically in this case. I wonder, why cell provider fails to fetch data and how to fix this in an efficient way.
@objc func handleAdd() {
// Add item to Core Data.
let context = moc
let entity = Item.entity()
let item = Item(entity: entity, insertInto: context)
item.name = "(letters[counter])" // Adds letters of the alphabet.
counter += 1
try! context.save()
// Manually fetching right after saving doesn’t seem efficient.
try! fetchedResultsController.performFetch()
}
question from:
https://stackoverflow.com/questions/65932897/uitableviewdiffabledatasource-cell-provider-fails-to-fetch-newly-added-managed-o 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…