As of iOS 13, the easiest way to keep a UITableView
in sync with a NSFetchedResultsController
seems to be with snapshots.
The NSFetchedResultsController vends a snapshot reference to its delegate whenever the managedObjectContext reports additions, deletions, or updates. When using snapshots (NSDiffableDataSourceSnapshot
), there is only one FRC delegate method that needs to be implemented: controller(_:didChangeContentWith:)
. In order to make that delegate method work, the UITableViewDiffableDataSource and the Snapshot has to be typed <String, NSManagedObjectID>
.
It works mostly.
But what if the entire table needs to be updated? Using tableView.reloadData()
or frc.performFetch()
seems anti-pattern.
edit
I manually built a snapshot, and call apply when necessary. But since my snapshot is based on NSFetchedResultsSectionInfo objects, it seems like I'm duplicating what the FRC already has available: Hashable section titles, and Hashable NSManagedObjectIDs
I apologize for my previous (deleted) answer. The snapshot is irrelevant in a Core Data context.
The purpose of NSFetchedResultsController
in conjunction with Core Data
is to update the UI when the NSManagedObjectContext
is saved.
To be able to control the animation of the diffable data source (to work around the ridiculous behavior) you have to subclass UITableViewDiffableDataSource
and add a property animatingDifferences
. Further adopt NSFetchedResultsControllerDelegate
in the subclass (not in the view controller).
class DiffableCoreDataSource: UITableViewDiffableDataSource<String,NSManagedObjectID> {
var animatingDifferences = false
}
extension DiffableCoreDataSource : NSFetchedResultsControllerDelegate
{
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
apply(snapshot as NSDiffableDataSourceSnapshot<String, NSManagedObjectID>, animatingDifferences: animatingDifferences)
animatingDifferences = true // set it to the default
}
}
In the view controller set the delegate
of the FRC to the subclass assuming there is a dataSource
property representing DiffableCoreDataSource
frc.delegate = dataSource
If a record is updated set dataSource.animatingDifferences
to false
right before saving the context.
To reload the entire table view call frc.performFetch()
. Never call reloadData()
on the table view.