首页 > 解决方案 > 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?

标签: ioscore-dataios13

解决方案


WWDC 视频暗示我们应该用 和 的泛型类型声明数据StringNSManagedObjectID。那对我不起作用;我可以通过动画和行更新获得合理行为的唯一方法是使用自定义值对象作为数据源的行标识符。

使用NSManagedObjectID作为项目标识符的快照的问题在于,尽管获取的结果委托被通知与该标识符关联的托管对象的更改,但它提供的快照可能与我们可能应用的前一个快照没有什么不同数据源。当底层数据发生变化并解决单元格更新问题时,将此快照映射到使用值对象作为标识符的快照上会产生不同的哈希值。

考虑一个待办事项列表应用程序的数据源,其中有一个包含任务列表的表视图。每个单元格显示一个标题和任务是否完成的一些指示。值对象可能如下所示:

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

数据源呈现这些项目的快照:

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
}

假设一个获取的结果控制器,它可以被分组,委托传递一个快照类型为StringNSManagedObjectID。可以将其操作为StringTaskItem(用作行标识符的值对象)的快照以应用于数据源:

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)
}

初始performFetchinviewDidLoad更新没有动画的表视图。此后的所有更新,包括仅刷新单元格的更新,都适用于动画。


推荐阅读