Updates with UITableViewDiffableDataSource and NSFetchedResultsController?

I used the new iOS 13 UITableViewDiffableDataSource to hook up my UITableView, and have a NSFetchedResultsController updating it with the core data objects using the new controller:didChangeContentWithSnapshot: delegate. So far so good. The table view is populated and rows are inserted and removed as expected. But I don't get any updates for updated NSManagedObjects, only for inserted or removed NSManagedObjects. The delegate method is called when a NSManagedObject is updated in the Core Data, but the new snapshot is identical to the old one, as the order of the items is not changed and neither is the NSManagedObjectID returned by the snapshot. How am I expected to update unmoved rows?

Replies

Hmm, that sounds like it could be a bug. I'd suggest filing that with the Feedback Assistant ASAP. Still might have a chance of being fixed before GM.

I think I have the same issue.


I have a UITableView populated by a NSFetchedResultsController.

When I perform the fetch (fetchedResultsController.performFetch()), I call

final fileprivate func updateSnapshot(animated: Bool = true) {
     var diffableDataSourceSnapshot = NSDiffableDataSourceSnapshot<Int, SKPRCrewMemberMO>()
     diffableDataSourceSnapshot.appendSections([0])
     diffableDataSourceSnapshot.appendItems(fetchedResultsController.fetchedObjects ?? [])
     dataSource.apply(diffableDataSourceSnapshot, animatingDifferences: animated)
}


The table view is populated as normal and display all the items fetched. So far so good.


When I edit and save an NSManagedObject from a pushed view controller,

controllerDidChangeContent

is called. In this method, I update the snapshot (ie. I call the updateSnapshot above). But the table view is not refreshed. I have to manually call the tableView.reloadData() method to refresh the cells and have the updated content displayed.

DId you ever figure this out? I can only have it work for added/removed items - if their actual content changes then it doesn't report a change.

As soon as i wrote that message i realised it will never work for changes to fields inside the objects - as the datasource is of type <String, NSManagedObjectID> so its obvoiusly only going to change if objects change based on their ID and nothing else. RIP.

I think this is because the NSManagedObject's hash value doesn't change when a property changes. And the hash changing is what forces the UITableViewDiffableDataSource to draw a change. Unfortunately it appears you cannot override the hash function for NSManagedObject. My workaround was to make a wrapper that includes the values I want to trigger an update so the hash changes when any of these values change. It works well for me, but it would be nicer if the hash value could change if a property changes or for the diffable data source to have another mechanism to work more closely with NSFetchedResultsController.

Code Block swift
struct WrappedItem: Hashable { /* to trigger model change update */
let item: Item /* this is an NSManagedObject */
let name: String
}
/* this is the declaration for the snapshot which u */
var diffableDataSourceSnapshot = NSDiffableDataSourceSnapshot<Int, WrappedItem>()
func updateSnapshot() {
        diffableDataSourceSnapshot = NSDiffableDataSourceSnapshot<Int, WrappedItem>()
        diffableDataSourceSnapshot.appendSections([0])
        do {
            let mappedObjects = try (fetchedResultsController.fetchedObjects ?? []).map { (item) -> WrappedItem in
                return WrappedItem(item: item, name: item.name)
            }
            diffableDataSourceSnapshot.appendItems(mappedCollections)
        } catch {
            os_log(.error, "Error updating snapshot - %@", error.localizedDescription)
        }
        diffableDataSource?.apply(diffableDataSourceSnapshot)
    }
}


On iOS 14b2 / Xcode 12b2 I'm experiencing an object update now appearing in the snapshot as a delete and insert, anyone else seeing the same?
I recently run into row update issue discussed here.

When managed object attribute is updated, the change, initially, does not propagate to the view.

The blog post on SwiftLee "How-to use Diffable Data Sources with Core Data" shows how to solve the issue. Look for sample code calling this method

Code Block
snapshot.reloadItems(reloadIdentifiers)


For managed objects with attributes changed, you should add the managed object identifier by calling reloadItems method.
I am probably a year late.

I recently run into similar issue where, when attributes of a managed object changes, the view is not updated.

FRC's delegate method is called:
Code Block
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference)


The snapshot object contains the identifier (MOID) to the changed object as well!

In order to get my table cell refreshed, I use the implementation similar to the one discussed in "How-to use Diffable Data Sources with Core Data" by SwiftLee.

Look for the sample implementation of the delegate method there.

The gist is... for object with attributes updated, add them to the snapshot by calling
Code Block
snapshot.reloadItems(_:)