如何在不使用 UITableViewDiffableDataSource 删除和插入的情况下重新加载项目?

How can I reload items without removing and inserting with UITableViewDiffableDataSource?

我正在使用 UITableViewDiffableDataSource 在我的应用程序中实现搜索屏幕。每个单元格代表一个搜索命中并突出显示单元格标题中的搜索匹配项,有点像 Xcode 的“快速打开”window 突出显示其结果项的一部分。当在搜索字段中输入文本时,我更新了结果列表。结果在列表中随着相关性的变化而上下移动。

诀窍是每次搜索文本更改时我都需要强制每个单元格 re-render,因为新的搜索字符串意味着更新单元格标题的突出显示部分。但我不想为删除和插入设置动画,因为它仍然是同一个项目。如何使用快照告诉数据源它需要重新加载单元格?

我这样声明数据源:

@property (retain) UITableViewDiffableDataSource<NSString *, SearchHit *> *dataSource;

SearchHit代表一个搜索结果;它具有显示标题和要在标题中突出显示的范围数组的属性。它会覆盖 hashisEqual:,以便每个结果行都是唯一标识的。

我的代码看起来像这样:

-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
  NSArray<SearchHit *> *hits = [self fetchHits:searchText];
  NSDiffableDataSourceSnapshot<NSString *, SearchHit *> *snap = [[[NSDiffableDataSourceSnapshot alloc] init] autorelease];
  [snap appendSectionsWithIdentifiers:@[@""]];
  [snap appendItemsWithIdentifiers:hits];
  [snap reloadItemsWithIdentifiers:hits];
  [self.dataSource applySnapshot:snap animatingDifferences:YES];
}

起初我没有在那里调用 reloadItemsWithIdentifiers,然后一旦出现在结果列表中,任何单元格都不会改变。添加 reload 调用有所帮助,但现在大多数单元格不断更新一次。这听起来像是我代码中某处的逻辑错误,但我已经验证传递给快照的命中是正确的,而传递给数据源的单元格创建回调的命中是错误的。

This article by Donny Wals and this related Twitter thread 涉及 Steve Breen 建议解决此问题的方法是使项目标识符类型仅表示显示单元格所需的属性。所以我更新了 SearchHit 的散列和相等性比较,以包括标题中突出显示的部分,这是他们以前没有的。然后我在每次更新时为所有单元格删除和插入动画,这是我不想要的。

这似乎是 reloadItemsWithIdentifiers 应该做的...对吗?

示例项目 here GitHub。

diffable 数据源 API 可能不是对单元格本身产生动画效果的正确工具。它适用于细胞出现、消失和排序的动画。如果您的数据源有通过 Hashable 一致性表示的更改,api 会将其视为更改,delete/insert 等

我的建议是从项目标识符中删除搜索文本,让每个单元格观察搜索文本并影响动画或独立于数据源重绘。

正确的解决方案实际上是 API 的名称 - 您提供给数据源的对象应该是 标识符,就像数据库中的 rowid 值一样。在我的例子中,当项目标识符不代表我可以查找的数据库中的行时,我只需要将对象的状态保存在某种查找结构中,这样当我调用 reloadItemsWithIdentifiers 时,我从该结构中获取每个单元格的状态,而不是从数据源交给我的对象中获取。