前言
平时开发中,经常用到UITableView 和 UICollectionView 是很常用的 UI 控件,在过去我们通常需要实现 Data Sources 来配置数据源,虽然在简单的业务中我们可以愉快的实现各种需求,可是一旦业务复杂起来,比如数据源实时的增删改,我们经常会一不小心就遇到 NSInternalInconsistencyException(Data Source 和当前 UI 状态不一致)等奇奇怪怪的异常。
本文参考以下文章
WWDC 2019 Session 220:Advances in UI Data Sources
Data Source 新特性:基于 Diffable 实现局部刷新
目前使用情况
现在大多数开发中遇到列表页,我们都会实现UITableView 的UITableViewDataSource方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
以上为必须实现的代理方法
考虑的问题
在我们平时开发中肯定会经常用到,对这些数据的处理,增、删、改、查 的一些数据操作。同时会经常衍生出一些让我们比较头疼的问题,我们在进行操作时,首先要计算操作的数据在数据源的位置信息,然后在刷新我们计算出来的indexpath,或者更加粗暴的将整个数据源刷新。(繁琐的计算,更有稍不甚者会导致闪退,还有消耗性能的全部刷新)
新的数据源Diffable Data Source 新的API
UITableViewDiffableDataSource继承自NSObject(开发的时候尽量创建一个自己的类,来继承自它,方便我们整体去修改一些东西,这代码习惯,应该大家都懂吧哈哈)
它是用来维护UITableView的数据源,Section 和 Item 遵循 IdentifierType,从而确保每条数据的唯一性。所以我们在增、删、改、查 的一些数据操作时,直接根据IdentifierType获取到要操作的数据就可以了。
直接贴出实践的代码,来供参考(很多使用注意点都写在注释里面了,仔细看看注释哈)
初始化UITableView和UITableViewDiffableDataSource
#pragma mark - setter--geeter
- (TestDiffableDataSource *)diffableDataSource{
if (!_diffableDataSource) {
//cellProvider相当于cellForRowAtIndexPath
_diffableDataSource = [[TestDiffableDataSource alloc]initWithTableView:self.tableView cellProvider:^UITableViewCell * _Nullable(UITableView * _Nonnull tableView, NSIndexPath * _Nonnull indexPath, id _Nonnull data) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([UITableViewCell class])];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NSStringFromClass([UITableViewCell class])];
}
id test = [self->_diffableDataSource itemIdentifierForIndexPath:indexPath];
NSLog(@"itemIdentifier: %@ indexPath: %@",test,indexPath);
TestModel *model = (TestModel *)data;
cell.textLabel.text = model.title;
cell.detailTextLabel.text = model.subTitle;
return cell;
}];
}
return _diffableDataSource;
}
- (UITableView *)tableView{
if (!_tableView) {
_tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 88, self.view.frame.size.width, self.view.frame.size.height-88) style:UITableViewStylePlain];
//把tableView的数据源设置成自己封装的类diffableDataSource
_tableView.dataSource = self.diffableDataSource;
//设置tableView为本VC,进行一些操作
_tableView.delegate = self;
//去掉tableView的尾部多余的下划线
_tableView.tableFooterView = [[UIView alloc]init];
}
return _tableView;
}
有没有很意外的发现,cellForRowAtIndexPath方法在初始化UITableViewDiffableDataSource方法里实现了
接下来插入数据
//模拟网络请求
- (void)loadData{
self.mutableArr = [[NSMutableArray alloc]init];
for (int i = 0 ; i < 10; i++) {
TestModel *testModel = [[TestModel alloc]init];
testModel.title = [NSString stringWithFormat:@"这是第%i个标题",i];
testModel.subTitle = [NSString stringWithFormat:@"这是第%i个幅标题",i];
[self.mutableArr addObject:testModel];
}
[self reloadTableView];
}
//赋值给数据源,刷新tableView
- (void)reloadTableView{
//第一次的时候新创建的NSDiffableDataSourceSnapshot,这样相当于新创建的数据源
NSDiffableDataSourceSnapshot *snapshot = [[NSDiffableDataSourceSnapshot alloc]init];
//必须先创建Section才可以插入数据
[snapshot appendSectionsWithIdentifiers:@[@"0"]];
//给snapshot赋值ItemIdentifierTypes
[snapshot appendItemsWithIdentifiers:self.mutableArr];
//更新数据源的snapshot 刷新tableView
[self.diffableDataSource applySnapshot:snapshot animatingDifferences:YES];
}
我们简单的插入两条数据
- (void)insertNewObject:(id)sender {
NSDiffableDataSourceSnapshot *snapshot = self.diffableDataSource.snapshot;
//不可以每次都创建新的snapshot,否则数据都是新的!
//NSDiffableDataSourceSnapshot *snapshot = [[NSDiffableDataSourceSnapshot alloc] init];
//必须先创建Section才可以插入数据
[snapshot appendSectionsWithIdentifiers:@[@"1"]]; //SectionIdentifierType不可以重复
TestModel *testModel = [[TestModel alloc]init];
testModel.title = @"这是新插入的cell";
//往Section中添加数据,默认添加到最后一个Section中
[snapshot appendItemsWithIdentifiers:@[testModel]]; //ItemIdentifierType也不可以重复⚠️
//在指定位置之前或之后插入数据
//[snapshot insertItemsWithIdentifiers:<#(nonnull NSArray *)#> beforeItemWithIdentifier:<#(nonnull id)#>];
//[snapshot insertItemsWithIdentifiers:<#(nonnull NSArray *)#> afterItemWithIdentifier:<#(nonnull id)#>];
//往指定Section中添加数据
//[snapshot appendItemsWithIdentifiers:@[testModel]] intoSectionWithIdentifier:@"0"];
[self.diffableDataSource applySnapshot:snapshot animatingDifferences:YES completion:^{
//
}];
}
简单的修改数据
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
TestModel * item = [self.diffableDataSource itemIdentifierForIndexPath:indexPath];
item.title = @"修改了本行cell的title";
//不能新创建NSDiffableDataSourceSnapshot,直接获取到diffableDataSource中snapshot所有的ItemIdentifierType
NSDiffableDataSourceSnapshot *snapshot = self.diffableDataSource.snapshot;
//把要刷新的ItemIdentifierTypes放到数组就可以了
[snapshot reloadItemsWithIdentifiers:@[item]];
//直接把刷新的snapshot放到diffableDataSource,刷新就可以了,会根据ItemIdentifierTypes,刷新数组中添加的ItemIdentifierTypes
[self.diffableDataSource applySnapshot:snapshot animatingDifferences:YES];
}
简单的删除数据
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
id item = [self itemIdentifierForIndexPath:indexPath];
//这里注意一下,每次调用self.snapshot都会创建新的对象
NSDiffableDataSourceSnapshot *snapshot = self.snapshot;
NSDiffableDataSourceSnapshot *snapshot1 = self.snapshot; //测试
NSDiffableDataSourceSnapshot *snapshot2 = self.snapshot; //测试
//删除的时候不用指定Section,因为每一个item都是唯一的
[snapshot deleteItemsWithIdentifiers:@[item]];
[self applySnapshot:snapshot animatingDifferences:YES completion:^{
//
}];
//不可以再使用tableview的方法删除
//[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
当然这个新的API还有许多其他代理方法我就不一一实现了
最后
Diffable Data Source 之前只能依赖三方库的实现的 Features 得到官方原生 UIKIt 的支持,相信用不了多久便可以在生产业务中使用,如电商类购物车,即时聊天等频繁增删的业务场景可以很轻松实现,这是一个拥抱变化的 Apple,值得期待。
网友评论