Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
576 views
in Technique[技术] by (71.8m points)

core data - How to get a diffable snapshot from an NSFetchResultsController in iOS 13?

So here we are in WWDC 2019 video 230, and starting at about minute 14 it is claimed that NSFetchedResultsController now vends an NSDiffableDataSourceSnapshot, so we can just apply it directly to a diffable data source (UITableViewDiffableDataSource).

But this is not quite what they say, or what we get. What we get, in the delegate method controller(_:didChangeContentWith:), is an NSDiffableDataSourceReference. How do we get from this to an actual snapshot, and what should my diffable data source generic types be?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The WWDC video implies that we should declare the data source with generic types of String and NSManagedObjectID. That is not working for me; the only way I can get sensible behaviour with animations and row updates is by using a custom value object as the row identifier for the data source.

The problem with a snapshot using NSManagedObjectID as the item identifier is that, although the fetched results delegate is notified of changes to the managed object associated with that identifier, the snapshot that it vends may be no different from the previous one that we might have applied to the data source. Mapping this snapshot onto one using a value object as the identifier produces a different hash when underlying data changes and solves the cell update problem.

Consider a data source for a todo list application where there is a table view with a list of tasks. Each cell shows a title and some indication of whether the task is complete. The value object might look like this:

struct TaskItem: Hashable {
    var title: String
    var isComplete: Bool
}

The data source renders a snapshot of these items:

typealias DataSource = UITableViewDiffableDataSource<String, TaskItem>

lazy var dataSource = DataSource(tableView: tableView) { tableView, indexPath, item in {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    cell.textLabel?.text = item.title
    cell.accessoryType = item.isComplete ? .checkmark : .none
    return cell
}

Assuming a fetched results controller, which may be grouped, the delegate is passed a snapshot with types of String and NSManagedObjectID. This can be manipulated into a snapshot of String and TaskItem (the value object used as row identifier) to apply to the data source:

func controller(
    _ controller: NSFetchedResultsController<NSFetchRequestResult>,
    didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference
) {
    // Cast the snapshot reference to a snapshot
    let snapshot = snapshot as NSDiffableDataSourceSnapshot<String, NSManagedObjectID>

    // Create a new snapshot with the value object as item identifier
    var mySnapshot = NSDiffableDataSourceSnapshot<String, TaskItem>()

    // Copy the sections from the fetched results controller's snapshot
    mySnapshot.appendSections(snapshot.sectionIdentifiers)

    // For each section, map the item identifiers (NSManagedObjectID) from the
    // fetched result controller's snapshot to managed objects (Task) and
    // then to value objects (TaskItem), before adding to the new snapshot
    mySnapshot.sectionIdentifiers.forEach { section in
        let itemIdentifiers = snapshot.itemIdentifiers(inSection: section)
            .map {context.object(with: $0) as! Task}
            .map {TaskItem(title: $0.title, isComplete: $0.isComplete)}
        mySnapshot.appendItems(itemIdentifiers, toSection: section)
    }

    // Apply the snapshot, animating differences unless not in a window
    dataSource.apply(mySnapshot, animatingDifferences: view.window != nil)
}

The initial performFetch in viewDidLoad updates the table view with no animation. All updates thereafter, including updates that just refresh a cell, work with animation.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...