視圖控制器在狀態保存和恢復過程中起重要作用。 狀態保留記錄您的應用程序在暫停之前的配置,以便可以在后續應用程序啟動時恢復配置。 將應用程序恢復到其先前的配置可為用戶節省時間,并提供更好的用戶體驗。
保存和恢復過程大多是自動的,但你需要告訴iOS應用程序的哪些部分保留。 保存應用程序視圖控制器的步驟如下:
- (必需)將恢復標識符分配給要保留其配置的視圖控制器;
- (必需)告訴iOS如何在啟動時創建或定位新的視圖控制器對象;
- (可選)對于每個視圖控制器,存儲將該視圖控制器返回到其原始配置所需的任何特定配置數據;
標記視圖控制器以進行保存
UIKit僅保留您要讓它保留的視圖控制器。 每個視圖控制器都有一個restorationIdentifier屬性,默認值為nil。 將該屬性設置為有效字符串會告訴UIKit應保留視圖控制器及其視圖。 您可以以編程方式或在故事板文件中分配恢復標識符。
當分配恢復標識符時,請記住視圖控制器層次結構中的所有父視圖控制器也必須具有恢復標識符。 在保存過程中,UIKit從窗口的根視圖控制器開始,并遍歷視圖控制器層次結構。 如果該層次結構中的視圖控制器不具有恢復標識符,則忽略視圖控制器及其所有子視圖控制器和呈現的視圖控制器。
選擇有效恢復標識符
UIKit使用您的恢復標識符字符串以后重新創建視圖控制器,因此選擇可以容易地識別您的代碼的字符串。 如果UIKit無法自動創建一個視圖控制器,它會要求您創建它,為您提供視圖控制器及其所有父視圖控制器的恢復標識符。 這個標識符鏈表示視圖控制器的恢復路徑,是你如何確定請求哪個視圖控制器。 恢復路徑在根視圖控制器處開始,并且包括直到并且包括所請求的視圖控制器的每個視圖控制器。
恢復標識符通常只是視圖控制器的類名。 如果在許多地方使用相同的類,則可能需要分配更有意義的值。 例如,您可以基于由視圖控制器管理的數據分配字符串。
每個視圖控制器的恢復路徑必須是唯一的。 如果容器視圖控制器有兩個子節點,則容器必須為每個子節點分配唯一的恢復標識符。 UIKit中的一些容器視圖控制器自動消除其子視圖控制器的歧義,允許您為每個子項使用相同的恢復標識符。 例如,UINavigationController類根據其在導航堆棧中的位置向每個子項添加信息。
不包括視圖控制器組
要從恢復過程中排除整個視圖控制器組,請將父視圖控制器的恢復標識符設置為nil。 圖7-1顯示了將恢復標識符設置為nil對視圖控制器層次結構的影響。 缺少保留數據阻止了視圖控制器以后被恢復。
圖7-1從自動保存過程中排除視圖控制器
排除一個或多個視圖控制器在后續還原期間不會完全刪除它們。 在啟動時,仍然創建作為應用程序默認設置的一部分的任何視圖控制器,如圖7-2所示。 這樣的視圖控制器以其默認配置重新創建,但仍然創建它們。
圖7-2加載默認的視圖控制器集
從自動保存過程中排除視圖控制器不會阻止您手動保留它。 在恢復歸檔中保存對視圖控制器的引用會保留視圖控制器及其狀態信息。 例如,如果圖7-1中的應用程序委托保存了導航控制器的三個子代,則它們的狀態將被保留。 在恢復期間,應用程序代理可以重新創建這些視圖控制器并將它們推送到導航控制器的堆棧。
保留視圖控制器的視圖
一些視圖具有與視圖相關但與父視圖控制器無關的附加狀態信息。 例如,滾動視圖具有您可能希望保留的滾動位置。 雖然視圖控制器負責提供滾動視圖的內容,但是滾動視圖本身負責保持其可視狀態。
要保存視圖的狀態,請執行以下操作:
- 為視圖的restoredIdentifier屬性分配一個有效的字符串。
- 使用來自還具有有效恢復標識符的視圖控制器的視圖。
- 對于表視圖和集合視圖,請分配采用UIDataSourceModelAssociation協議的數據源。
為視圖分配恢復標識符告訴UIKit它應該將該視圖的狀態寫入保留存檔。 當視圖控制器以后恢復時,UIKit還恢復具有恢復標識符的任何視圖的狀態。
在啟動時恢復視圖控制器
在啟動時,UIKit嘗試將您的應用恢復到之前的狀態。 當時,UIKit要求您的應用程序創建(或定位)包含保留的用戶界面的視圖控制器對象。 UIKit在嘗試查找視圖控制器時按以下順序進行搜索:
- 如果視圖控制器有一個恢復類,UIKit要求該類提供視圖控制器。 UIKit調用關聯的恢復類的viewControllerWithRestorationIdentifierPath:coder:方法來檢索視圖控制器。 如果該方法返回nil,則假定應用程序不想重新創建視圖控制器,UIKit停止查找它。
- 如果視圖控制器沒有恢復類,UIKit要求應用程序代理提供視圖控制器。 UIKit調用應用程序:viewControllerWithRestorationIdentifierPath:coder:您的應用程序委托的方法來查找沒有恢復類的視圖控制器。 如果該方法返回nil,UIKit試圖隱式地查找視圖控制器。
- 如果具有正確恢復路徑的視圖控制器已存在,UIKit將使用該對象。 如果您的應用程序在啟動時創建視圖控制器(以編程方式或通過故事板加載它們),并且這些視圖控制器有恢復標識符,UIKit隱式地基于它們的恢復路徑找到它們。
- 如果視圖控制器最初從故事板文件加載,UIKit使用保存的故事板信息來定位和創建它。 UIKit將有關視圖控制器的storyboard的信息保存在恢復存檔中。 在恢復時,UIKit使用該信息來定位相同的故事板文件,并且如果沒有通過任何其他方式找到視圖控制器,則實例化相應的視圖控制器。
向視圖控制器分配恢復類可以防止UIKit隱含地搜索該視圖控制器。 使用恢復類可以更好地控制是否要創建視圖控制器。 例如,如果您的類確定不應重新創建視圖控制器,則您的viewControllerWithRestorationIdentifierPath:coder:方法可以返回nil。 當沒有恢復類時,UIKit將盡其所能找到或創建視圖控制器并恢復它。
當使用恢復類時,您的viewControllerWithRestorationIdentifierPath:coder:方法應該創建一個類的新實例,執行最小初始化,并返回結果對象。 清單7-1顯示了如何使用此方法從故事板加載視圖控制器的示例。 因為視圖控制器最初從故事板加載,所以此方法使用UIStateRestorationViewControllerStoryboardKey鍵從歸檔中獲取故事板。 請注意,此方法不會嘗試配置視圖控制器的數據字段。 該步驟稍后發生在視圖控制器的狀態被解碼時。
清單7-1在恢復期間創建一個新的視圖控制器
+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder {
MyViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb) {
vc = (PushViewController*)[sb instantiateViewControllerWithIdentifier:@"MyViewController"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [MyViewController class];
}
return vc;
}
重新分配恢復標識符和恢復類是手動重新創建視圖控制器時采用的好習慣。 恢復恢復標識符的最簡單的方法是抓取identifierComponents數組中的最后一個項目,并將其分配給視圖控制器。
對于在啟動時從應用程序的主storyboard文件創建的對象,請不要創建每個對象的新實例。 讓UIKit隱式找到這些對象或使用應用程序:viewControllerWithRestorationIdentifierPath:coder:您的應用程序委托的方法來查找現有對象。
編碼和解碼視圖控制器的狀態
對于每個要保存的對象,UIKit調用對象的encodeRestorableStateWithCoder:方法給它一個保存其狀態的機會。 在恢復過程中,UIKit調用匹配的decodeRestorableStateWithCoder:方法來解碼該狀態并將其應用于對象。 這些方法的實現是可選的,但建議為您的視圖控制器。 您可以使用它們來保存和恢復以下類型的信息:
- 對任何正在顯示的數據的引用(不是數據本身)
- 對于容器視圖控制器,引用其子視圖控制器
- 有關當前選擇的信息
- 對于具有用戶可配置視圖的視圖控制器,有關該視圖的當前配置的信息。
在編碼和解碼方法中,您可以對對象和編碼器支持的任何數據類型進行編碼。 對于除視圖和視圖控制器之外的所有對象,對象必須采用NSCoding協議并使用該協議的方法來寫入其狀態。 對于視圖和視圖控制器,編碼器不使用NSCoding協議來保存對象的狀態。 相反,編碼器保存對象的恢復標識符并將其添加到可保留對象的列表中,這導致調用該對象的encodeRestorableStateWithCoder:方法。
你的視圖控制器的encodeRestorableStateWithCoder:和decodeRestorableStateWithCoder:方法必須在其實現的某個點調用super。 調用super給父類一個機會來保存和恢復任何附加信息。 清單7-2顯示了保存用于標識指定視圖控制器的數值的這些方法的示例實現。
代碼7-2對視圖控制器的狀態進行編碼和解碼。
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
[super encodeRestorableStateWithCoder:coder];
[coder encodeInt:self.number forKey:MyViewControllerNumber];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
self.number = [coder decodeIntForKey:MyViewControllerNumber];
}
編碼器對象在編碼和解碼過程中不共享。 每個具有可保存狀態的對象接收其自己的編碼器對象。 使用唯一的編碼器意味著你不必擔心你的鍵之間的命名空間沖突。 但是,不要使用UIApplicationStateRestorationBundleVersionKey,UIApplicationStateRestorationUserInterfaceIdiomKey和UIStateRestorationViewControllerStoryboardKey鍵名稱自己。 UIKit使用這些鍵來存儲有關視圖控制器狀態的附加信息。
保存和恢復視圖控制器的提示
當您在視圖控制器中添加對狀態保存和恢復的支持時,請考慮以下準則:
- 請記住,您可能不想保留所有視圖控制器。 在某些情況下,保留視圖控制器可能沒有意義。 例如,如果應用程序正在顯示更改,則可能需要取消操作并將應用程序恢復到上一屏幕。 在這種情況下,您不會保留要求新密碼信息的視圖控制器。
- 避免在恢復過程中交換視圖控制器類。 狀態保存系統對它保存的視圖控制器的類進行編碼。 在恢復期間,如果您的應用程序返回其類與原始對象不匹配(或不是其子類)的對象,則系統不會要求視圖控制器解碼任何狀態信息。 因此,換出完全不同的舊視圖控制器不會恢復對象的完整狀態。
- 狀態保留系統期望您使用它們的意圖的視圖控制器。 恢復過程依賴于視圖控制器的包含關系來重建接口。 如果不正確使用容器視圖控制器,保存系統將無法找到您的視圖控制器。 例如,從不將視圖控制器的視圖嵌入在不同的視圖中,除非在相應的視圖控制器之間存在包含關系。