重新加载一批 UICollectionView 或 UITableView 更改的好算法?

Posted

技术标签:

【中文标题】重新加载一批 UICollectionView 或 UITableView 更改的好算法?【英文标题】:Good algorithm for reloading a batch of UICollectionView or UITableView changes? 【发布时间】:2013-01-07 09:59:54 【问题描述】:

设置:我有一个由简单数组数据源支持的 UICollectionViewUITableView。我在控制器中保留了该数据源的副本。

现在,我收到来自系统的通知,提示有新数据可用。我得到一个新数组,其中可能已添加、删除和更改位置。

所以现在我有两个数据对象:

之前的数组与 UI 当前显示的内容同步 添加、删除、移动项目的新数组

为了使 UI 与新数组同步,我需要生成一堆 UI 调用。在UICollectionView 的情况下,这些是

- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths

UITableView 也有一组类似的方法。

我特别不想重新加载整个表格,因为这比只处理几个项目更昂贵。

所以,问题是:给定先前和新的数据源数组,我如何生成正确的 UI 调用集,以及何时“换出”旧数据源为新数据源?

【问题讨论】:

调用者(正在生成新数组的人)是否有可能向我们发送一些信息,例如修改的单元格数量?因为他是唯一知道这些信息的人。这将导致最优化的解决方案。 是的,我可以控制调用者,他们可能会发送除整个列表之外的已更改模型对象列表之类的内容,或任何其他合适的内容。 @Jaanus 查看我的答案,在尝试了不同的可能性后,我已经解决了同样的问题。 【参考方案1】:

我认为这在很大程度上等同于 diff / patch 问题,其目的是找到一个文本文件与另一个文本文件之间的最小更改次数,并且然后应用这些更改。在这种情况下,实现定义了操作:

添加或插入 删除 改变

...但不是移动。省略 move 的原因对我来说并不是很明显,但我强烈怀疑包含 move 将需要非常昂贵的计算才能找到最佳移动。

因此,如果我们将操作限制在上面列出的范围内,An Algorithm for Differential File Comparison 或其descendents 中描述的 Hunt-McIlroy 算法将找到一组接近最优的更改。

你的问题和经典的 diff / patch 的区别在于你有一个二维表,而 diff / patch 处理一维项目集(文本行)。将 2-D 问题转换为 1-D 问题的最佳方法将取决于数据表中倾向于进行的更改的特定特征。

例如,如果表由 m 列组成 n 行,并且更改倾向于按行分组,或者将行作为一个整体插入或删除,那么您将可能最好将表格视为文本文件并逐行执行 diff。或者,如果更改倾向于按列分组或插入或删除列,您可以逐列执行 diff。如果更改包括插入或删除单个单元格(结果导致后续单元格向右或向左移动),您可以将表格视为表格中的每个单元格位于文本文件的单独行上,从而使表格线性化以行优先或列优先的顺序。

但是,在不知道您在这方面的问题的详细信息的情况下,我倾向于避免过早的优化。因此,如果 m n 则我倾向于从逐行实现 Hunt-McIlroy 算法开始,如果 n 则逐列实现 m,然后在决定是否需要更复杂的算法版本或将您的问题映射到 Hunt-McIlroy 解决方案之前对正在使用的应用程序进行分析。

关于各种diff算法的很好的讨论可以找到here on ***。

【讨论】:

UITableView 和 UICollectionView 都处理一维数据源,其中一列中只有一个行列表。 在这种情况下,Hunt-McIlroy 算法的简单一维实现可能就足够了。【参考方案2】:

我开发了一个应用程序来处理我能够解决的非常相似的问题。我期待一个复杂的解决方案,但它真的很简单。解决方法如下:

1) 创建一个新数组,您将在其中接收新项目并将其命名为“moreItems”,当然也可以合成它。

@property (nonatomic, readonly) NSMutableArray *moreItems;

2) 在与您的 TableView 或 CollectionView 链接的 ViewController 的 viewDidLoad 中,分配/初始化此数组:

moreItems = [[NSMutableArray alloc] init];

3) 现在您需要将现有数组添加到您刚刚创建的名为“moreItems”的新数组中。您可以使用“addObjectsFromArray”来做到这一点

[moreItems addObjectsFromArray:[channel items]];

'channel items' 包含它之前收到的对象,它会将这些项目添加到新创建的名为 'moreItems' 的数组中。我假设您正在从某个 Web 服务收集数据,因此您可以在 connectionDidFinsihLoading 中实现此步骤。

4) 现在更改 TableView/CollectionView 的数据源并将其替换为新数组“moreItems”

5) 下一个问题是您不想重新加载整个 tableview 并想处理新项目。要拥有此功能,您需要持久化项目。您可以随意使用归档或核心数据。

6) 已经获取的项目,您必须将它们持久化,让我们说存档。并在用户打开应用时在 tableview 中显示它们,同时它会抓取更多在 Web 服务上更新的项目。所以首先立即显示持久化的项目,然后处理新的项目。

7) 您需要寻找一些独特的对象,在我的情况下是“链接”,因为每个项目都有不同的链接,我可以在此基础上对它们进行排序和处理。您还需要使用

- (BOOL) isEqual:(id)object

比较网络服务上的链接和表格视图中已经存在的链接。此步骤是必要的,因为这样您的应用将不会添加带有链接的项目,这些链接已经在 tableview 中。

8) 如果每个项目都有某个日期,您可以使用该日期对它们进行排序,并使用 'sortUsingComparator' 将新的显示在顶部

9) 您还需要使用“[[self tableView] insertRowsAtIndexPath:rows withRowAnimation:UITableViewRowAnimationTop”来向用户显示新项目已添加到之前的项目之上。

希望这会有所帮助。

【讨论】:

【参考方案3】:

我特别不想重新加载整个表格,因为那是 比只处理几件物品要贵。

你确定这是真的吗?除非您有病态的情况,即屏幕上的每个单元格都可见并且有数百个,否则我很难相信“有效”方式会比“蛮力”方式更快。

调用reloadData 只会要求您的代理更新那些最终将在屏幕上可见的单元格,它不会导致整个表格视图重新创建其每个单元格。

如果您正在实施一些改变计算尺寸复杂性的方法(例如tableView:heightForRowAtIndexPath),那么使用“高效”方式您将不会获得任何好处,因为无论如何表格视图都必须重新计算其所有高度。

我不确定您正在寻找的解决方案是否能解决问题,但希望我错了。

【讨论】:

不使用 reloadData 的另一个原因是它只是立即显示新单元格,而不是动画 UI 中的更改。【参考方案4】:

如果您的数据源已经有差异信息,UITableView reloadRowsAtIndexPaths:withRowAnimation: 会降低性能密集度:

【讨论】:

以上是关于重新加载一批 UICollectionView 或 UITableView 更改的好算法?的主要内容,如果未能解决你的问题,请参考以下文章

在 ios 的 UITableView 或 UICollectionView 中重新加载数据的最佳位置是啥

UICollectionView insertItemsAtIndexPaths:似乎重新加载collectionview或自动滚动到顶部

在 UICollectionView 中重新加载数据的正确方法是啥?

在出现之前重新加载 UICollectionView 而不让用户看到更改

UICollectionView 在重新加载数据时滚动不平滑

重新加载数据后根据要求更新后未显示 uicollectionview 单元格