如何使用 diffable UITableView 更新表格单元格

Posted

技术标签:

【中文标题】如何使用 diffable UITableView 更新表格单元格【英文标题】:How to update a table cell using diffable UITableView 【发布时间】:2020-02-12 02:54:10 【问题描述】:

我将新的 NSDiffableDataSourceSnapshot 和 UITableViewDiffableDataSource 与 UITableView 一起使用。我在构建表格时没有问题,但是当单元格中显示的数据发生变化时,我在更新单元格时遇到了问题。我还没有找到任何解释如何执行此操作的 Apple 文档。我尝试了以下方法:

self.currentSnapshot.reloadItems([Item(identifier: identifier)])
self.dataSource.apply(self.currentSnapshot)

我在 reloadItems 中收到以下错误:

-[__UIDiffableDataSourceSnapshot 中的断言失败 _reloadViewUpdatesForDiffUpdate:dataSource:ignoreInvalidItems:]

我已检查传递给 Item 初始化程序的标识符是否已存在于快照中。

这是我的 Item 类:

class Item: Hashable, Equatable 

    let identifier: String
    var matchWrapper: MatchWrapper

    init(matchWrapper: MatchWrapper) 
        self.identifier = matchWrapper.identifier
        self.matchWrapper = matchWrapper
    

    func hash(into hasher: inout Hasher) 
        hasher.combine(self.identifier)
    

    static func == (lhs: ScoresViewController.Item, rhs: ScoresViewController.Item) -> Bool 
        return lhs.identifier == rhs.identifier
    

有什么建议吗?

【问题讨论】:

我知道这个问题有点老了,但我想我会插话的。我不建议为您的项目标识符使用类。这会给你带来麻烦,因为它们是引用类型。这可能是您崩溃的原因,但如果不查看您修改数据的方式/位置,就很难判断。此外,您要确保对要在单元格中显示的所有字段进行散列和比较 (==)。这是数据源知道任何字段已更改的唯一方法。如果您有更多问题,请告诉我。 @RobertCrabtree 你的意思是在这种情况下matchWrapper 必须是散列和== 运算符的一部分吗?当预期结果是更新现有项目时,这不会导致数据源识别“更新”对象,其中只有 matchWrapper 的内容已更改为新项目? 【参考方案1】:

我也遇到过类似的问题,经过大量调试和尝试不同的事情后,我得出的结论是,我认为您不应该使用reloadItemsreloadItems 在我的测试中实际上似乎没有做任何事情。单元提供者总是提供旧数据,如果你有一个基于标识符的哈希函数和一个检查标识符以外的事物是否相等的等价函数,你会得到一个错误。

有两件事你可以尝试

    如果你真的想在 cellProvider 中使用 reload items 而不是使用闭包提供的数据,请使用你自己的数据源作为事实源来布置单元格。 (在我看来,这种方式违背了 diffableDataSource 的目的)。你最终会错过 DiffableDataSource 的一些强大功能

    而不是像这样编写代码:

var snapshot = tableView.snapshot()
let item = items[indexPath.row]
snapshot.reloadItems([item]) 

你可以这样做

let snapshot: NSDiffableDataSourceSnapshot<Section,Item> = .init() 
// assuming wherever you're storing your data is already updated 
snapshot.appendItems([items])
dataSource.apply(snapshot)  

这应该只刷新快照中发生更改的项目。据我了解,tableView 的当前快照与新创建的快照进行比较,只有不同的项目才应该更新。我在这里可能是错的,但这对我有用。

我看到的其他建议:

编写您的散列函数,结合所有可以在视图中更改的值。
func hash(into hasher: inout Hasher) 
  hasher.combine(id)
  hasher.combine(name)
  hasher.combine(title)

但是,如果你走这条路线,这意味着 reloadItems 将抛出一个错误报告,指出数据源中不存在该数据,因此你必须手动添加并删除旧数据

【讨论】:

【参考方案2】:

这对我有用:

func handleNewItems(_ newItems: [SomeItem]) 
    var snapShot = dataSource.snapshot()
    let diff = newItems.difference(from: snapShot.itemIdentifiers)
    let currentIdentifiers = snapShot.itemIdentifiers
    guard let newIdentifiers = currentIdentifiers.applying(diff) else 
        return
    
    snapShot.deleteItems(currentIdentifiers)
    snapShot.appendItems(newIdentifiers)
    dataSource.apply(snapShot, animatingDifferences: true)

【讨论】:

Noam 的回答对我有用。我试图在 macOS 中使用 tableView diffableDataSource 使用“reloadItems ...” API,但它一直给我一个异常。对我来说唯一的解决方案是从快照中删除项目,应用快照,将项目重新插入快照,然后再次应用。

以上是关于如何使用 diffable UITableView 更新表格单元格的主要内容,如果未能解决你的问题,请参考以下文章

支持 iOS 12 和 13 时的 UITableView 和 Diffable 数据源

iOS 13 上 TableView 错误的 Diffable 数据源:移动的关联不一致

领域和 Diffable 数据源

具有多个单元格的 Collectionview 和 diffable 部分

diffable 数据源节标题在更新期间闪烁

在表/集合视图控制器及其关联的 diffable 数据源子类之间共享数据模型的好方法是啥?