iOS UI狀態儲存和恢復(三)

QiShare發表於2019-08-12

級別: ★★☆☆☆
標籤:「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地址


小編微信:可加並拉入《QiShare技術交流群》。

iOS UI狀態儲存和恢復(三)

關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)

推薦文章:
iOS UI狀態儲存和恢復(二)
iOS UI狀態儲存和恢復(一)
Swift 運算子
iOS 中精確定時的常用方法
Sign In With Apple(一)
演算法小專欄:動態規劃(一)
Dart基礎(一)
Dart基礎(二)
Dart基礎(三)
Dart基礎(四)
奇舞週刊

相關文章