TLDR
SGListAnimator 为您的表格和集合视图提供动画过渡,因此您无需调用 reloadData
,这会让 UI 瞬间跳转至新的状态而没有任何动画。
问题所在
我们都在我们的 iOS 应用中使用表格和集合视图来显示事物列表。它们是伟大的组件,并且附带了一组动画更改的方法。但是,您需要先知道更改是什么,然后才能知道调用哪些动画方法。
如果更改是您应用程序推动的,那么知道它们很容易。然而,我们经常显示来自其他地方的列表,例如服务器。当我们收到更新后的列表时,我们知道它可能仅改变了一点点——这里添加了一行,那里进行了删除或移动。不幸的是,比较新旧列表以找到所有差异并不那么容易。如果您的算法错过了任何内容,则应用程序会崩溃。
如果您的表格视图由 Core Data 表支持,则可以使用 NSFetchedResultsController
,它提供了可以映射到动画方法的代理回调。其他人大多只是放弃,并调用 reloadData
。
SGListAnimator
SGListAnimator 比较您的旧列表和新列表,检测不同类型的更改(有 7 种),并在一个或多个动画块中对表或集合中的适当动画方法进行调用。
需求
要使用它,您需要
- 使用具有唯一标识符的对象表示您的部分 -- 标题字符串效果很好。
- 使用也具有唯一标识符的对象表示您的行/项目。这意味着它们从
hash
和isEqual
返回适当的值。通常,您已经在使用符合要求的数据模型对象。 - 不要有重复的部分或项目。
- 将您的表格/集合视图的备份数据保存在数组属性中,这样当您有更新后的数据时,您可以方便地使用旧的和新的数组。
安装
您可以通过 Cocoapods 整合,使用 Git 子模块,或只需将此存储库中的相关文件复制到您的项目中。这些文件位于 SGListAnimator
文件夹中。
target 'MyTarget' do
pod 'SGListAnimator'
end
示例代码
我们有一个方便的类方法来帮助您入门。您只需要保存一个您的部分标题数组,以及另一个数组,该数组包含每个部分的项
- (void)updateTableView {
// Save a reference to the previous content
NSArray *oldSectionItems = self.sectionItems;
NSArray *oldSectionTitles = self.sectionTitles;
// Update your backing arrays to the new content
self.sectionItems = ...
self.sectionTitles = ...
// And do the transition
[SGListAnimator transitionTableView:self.tableView
fromSectionItems:oldSectionItems
titles:oldSectionTitles
toSectionItems:self.sectionItems
titles:self.sectionTitles
];
}
// Obviously, your table view data source methods will all use
// `self.sectionItems` and `self.sectionTitles`.
如果您没有将列表分成部分,则在底层仍然有一个没有标题的部分。您的代码可能如下所示
- (void)updateTableView {
// Save a reference to the previous content
NSArray *oldItems = self.tableViewItems;
// Update your backing array to the new content
self.tableViewItems = ...
// And do the transition
[SGListAnimator transitionTableView:self.tableView
fromSectionItems:oldItems
titles:@[@""]
toSectionItems:self.tableViewItems
titles:@[@""]
];
}
// Use self.tableViewItems in the data source methods.
要启用 SGListAnimator 的一些功能,如支持移动,您需要使用 SGListAnimator 对象,并保留您的支持数据在动画器的 currentSections
属性中。这允许动画器在需要时在底层将转换分成多个子转换。您将为每个部分创建一个简单的 SGListSection
对象;它将标题和项目数组结合在一个方便的对象中。
以下是一个更完整的示例,所有 3 种类型的移动都已打开
#import "SGListAnimator.h"
#import "SGListSection.h"
...
@property (nonatomic) SGListAnimator *animator;
- (void)viewDidLoad {
[super viewDidLoad];
...
self.animator = [SGListAnimator new];
self.animator.tableView = self.tableView;
self.animator.doSectionMoves = YES;
self.animator.doIntraSectionMoves = YES;
self.animator.doInterSectionMoves = YES;
}
- (void)updateTableView {
// Figure out what your new content is, creating each SGListSection
// object with code like this:
...
SGListSection *section = [SGListSection sectionWithTitle:... items:...];
[newSections addObject:section];
...
[self.animator transitionTableViewToSections:newSections];
}
// Use self.animator.currentSections in the data source methods, like:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.animator.currentSections[section].items.count;
}
...
功能
- 处理部分的删除、插入和移动。处理行/项目删除、插入、移动在部分内的移动,以及从一个部分到另一个部分的移动。
- 同时支持
UITableView
和UICollectionView
。 - 允许您选择支持三种类型的移动。移动需要更复杂的计算,可能成本更高,这取决于您的列表大小和列表上的变动程度。许多应用不一定需要所有类型的移动。
- 已彻底进行单元测试和压力测试。包含的演示应用使用随机数据进行了压力测试,这确实在开发期间发现了一个错误。
- 支持Swift和Objective-C。我们在这里对Swift的利用率不高,如果您有什么改进建议,请告诉我们。演示应用中的表格视图是用Swift编写的,而集合视图是用Objective-C编写的。
- 在iOS 8和9上进行了测试。我认为应该能在iOS 7上也正常运行。
- 没有依赖项。
待办事项
- 动画器尚未在大量列表(即数千个项目)上进行大量测试/分析。使用分页等方式避免向客户端推送大量对象是一个好的方法,因此希望大多数人每次最多只处理一百或两百个项目。
- 为客户端代码提供一个钩子,以便微调使用的动画会很好。目前对于表格视图,一些合理的选项是硬编码的,而集合视图使用默认行为。