iosswiftcore-dataios13

Is it possible to source an up-to-date snapshot from an NSFetchedResultsController


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


Solution

  • 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.