iOS UI状态保存和恢复(三)

avatar
奇舞团移动端团队 @奇舞团

级别: ★★☆☆☆
标签:「iOS」「UIStateRestoration」
作者: 沐灵洛
审校: QiShare团队


前面两篇我们介绍了UI状态保存和恢复的流程,UIStateRestoration协议类的方法,适用场景,调试策略,UIApplication,UIViewController,UIView关于UIStateRestoration协议所提供的接口方法以及如何实现UI状态保存和恢复。本篇我们将介绍UIStateRestoration协议类中的UIDataSourceModelAssociation 协议。

关于UIDataSourceModelAssociation协议

引用官网的解释

Your data source objects can adopt this protocol to assist a corresponding table or collection view during the state restoration process. Those classes use the methods of this protocol to ensure that the same data objects (and not just the same row indexes) are scrolled into view and selected. //你的数据源对象可以实现这个协议,在状态恢复的过程中去支持相关的table or collection view;这些实现了该协议的类,使用这个协议的方法去保证相同的数据对象,(而不仅仅是相同的行的索引)被滚动到视图并且被选中。 Before you can implement this protocol, your app must be able to identify data objects consistently between app launches. This requires being able to take some identifying marker of the object and convert that marker into a string that can then be saved with the rest of the app state. For example, a Core Data app could convert a managed object’s ID into a URI that it could then convert into a string. //在你实现这个协议之前,你的App必须能够在App启动之间,一直(总是可以)辨别出数据源对象。这就要求对象能够有一些辨认标识,并且可以把标识转换为当App状态不活跃时能够被存储的字符串; Currently, only the UITableView and UICollectionView classes support this protocol. You would implement this protocol in any objects you use as the data source for those classes. If you do not adopt the protocol in your data source, the views do not attempt to restore the selected and visible rows. //目前,只有 UITableView 和 UICollectionView 类 支持这个协议。你将可以实现这个协议在任何你用来作为UITableView 和 UICollectionView数据源的对象中,如果在你的数据源对象中不实现这个协议,那么视图将不会试着去恢复选中的和可见rows;

我们可以获取到的主要信息有:

  • 只有 UITableViewUICollectionView 类支持这个协议。
  • 我们的数据源中的每个数据对象(model)必须具备唯一辨认标识。
  • 使用这个协议的方法去保证相同的数据对象,(而不仅仅是相同的行的索引)被滚动到视图并且被选中。举个场景的例子:TableView的数据源对象在上次保存时,所保存的行的索引,可能会因为在当前运行周期内数据源中数据的变动发生变化。从而导致当前选中的行所对应的数据并非上次保存时的数据。
  • 若需要使用UIDataSourceModelAssociation,则:实现了UITableView 和 UICollectionView数据源协议的对象,负责实现这个协议的方法,否则不会生效。实际操作发现确实如此。

除了官网解释,在实际操作中发现还需要设置UITableView 或UICollectionView的restorationIdentifier,否则UIDataSourceModelAssociation协议方法不会被调用。关于UITableView的restorationIdentifier查阅官方文档如下:

To save and restore the table’s data, assign a nonempty value to the table view’s restorationIdentifier property. When its parent view controller is saved, the table view automatically saves the index paths for the currently selected and visible rows. If the table’s data source object adopts the UIDataSourceModelAssociation protocol, the table stores the unique IDs that you provide for those items instead of their index paths.

UITableView设置了restorationIdentifier,进行UI的保存时,tableView会自动存储当前选中和可见行的索引。补充:还会存储滚动偏移,并可以恢复。

UIDataSourceModelAssociation使用
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    InfoModel *model = [self.dataSource objectAtIndex:indexPath.row];
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(UITableViewCell.class) forIndexPath:indexPath];
    cell.textLabel.text = model.title;
    cell.restorationIdentifier = model.identifier;
    
    return cell;
}
- (NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)idx inView:(UIView *)view {
    //根据index 返回identifier
    NSString *identifier = nil;
    InfoModel *model = [self.dataSource objectAtIndex:idx.row];
    
    /*
     注释①
     if (idx && view) {
       identifier = model.identifier;
    }
    */
    if (idx.row == _currentPath.row && view) {
        identifier = model.identifier;
    }
    //若是不定义_currentPath追踪当前选中的cell.会多保存一个cell,目前尚未有答案。
    return identifier;
}
//此方法 恢复时调用
- (NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view {
    //根据identifier 返回index;
    NSIndexPath *indexPath = nil;
    if (identifier && view) {
        __block NSInteger row = 0;
        [self.dataSource enumerateObjectsUsingBlock:^(InfoModel *obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if ([obj.identifier isEqualToString:identifier]) {
                row = idx;
                *stop = YES;
            }
        }];
        indexPath = [NSIndexPath indexPathForRow:row inSection:0];
        _currentPath = indexPath;
        NSLog(@"当前选中的数据源对象标识是:%@,对象抬头是:%@",[self.dataSource[indexPath.row] identifier],[self.dataSource[indexPath.row] title]);
    }

    return indexPath;
}

上述代码方法-(NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)idx inView:(UIView *)view中注释①描述:此方法会在保存时调用两次,idx所返回的数据除了我们选中的行,还会返回一个其他行。
若是采用这种方式映射唯一标识,会出现保存了我们不需要的行的标识,导致恢复滑动位置失效,针对此问题目前笔者尚未有答案,查阅资料发现这个问题曾经是苹果的一个BUG,若是大家知道具体原因,欢迎评论和补充。目前在此基础上笔者自己想的解决办法:定义_currentPath追踪当前选中的cell,保存时根据_currentPath保存我们需要的标识,测试中发现可以解决问题。
QIRestorationDemo地址


推荐文章:
iOS UI状态保存和恢复(二)
iOS UI状态保存和恢复(一)
Swift 运算符
iOS 中精确定时的常用方法
Sign In With Apple(一)
算法小专栏:动态规划(一)
Dart基础(一)
Dart基础(二)
Dart基础(三)
Dart基础(四)
奇舞周刊