視圖控制器在保存和恢復(fù)過程中起著非常重要的作用。狀態(tài)保存記錄應(yīng)用中斷前的配置,在隨后應(yīng)用啟動恢復(fù)配置。將應(yīng)用恢復(fù)到先前的配置可以為用戶節(jié)省時(shí)間,并提供一個(gè)更好的用戶體驗(yàn)。
保存和恢復(fù)過程是自動的,但你需要告訴iOS,需要保留應(yīng)用的哪些部分。保存應(yīng)用視圖控制器的步驟如下:
· (必須)分配恢復(fù)標(biāo)示符到你想保存的視圖控制器上,參見標(biāo)記保存視圖控制器( Tagging View Controllers for Preservation)
· (必須)告訴iOS如何在啟動時(shí)創(chuàng)建或查找新視圖控制器對象,參見啟動時(shí)恢復(fù)視圖控制器( Restoring View Controllers at Launch Time)。
· (可選)每個(gè)視圖控制器,存儲任何特定配置數(shù)據(jù)使視圖控制器返回到原來的配置,參見視圖控制器狀態(tài)的編碼和解碼(Encoding and Decoding Your View Controller’s State)。
保存和恢復(fù)的過程的概述,參見iOS應(yīng)用編程指南(App Programming Guide for iOS)。
標(biāo)記保存視圖控制器
UIKit只保存你告訴它要保存的視圖控制器。每個(gè)視圖控制器有一個(gè)restorationIdentifier屬性,這個(gè)屬性的默認(rèn)值為nil。設(shè)置這個(gè)屬性值為有效字符串,告訴UIKit視圖控制器和視圖應(yīng)該要保存。可以以編程的方式或者在storyboard文件中設(shè)置恢復(fù)標(biāo)示符。
當(dāng)分配恢復(fù)標(biāo)示符時(shí),在視圖層級結(jié)構(gòu)中的所有的父視圖控制器必須有恢復(fù)標(biāo)示符。在保存過程中,UIKit從窗口的根視圖控制器開始并遍歷視圖控制器層級結(jié)構(gòu)。如果該層級結(jié)構(gòu)中一個(gè)視圖控制器沒有恢復(fù)標(biāo)示符,該視圖控制器和其所有子視圖控制器和present的視圖控制器都會被忽略。
選擇有效的恢復(fù)標(biāo)示符
UIKit使用恢復(fù)標(biāo)示符字符串在之后重新創(chuàng)建視圖控制器,所以必須選擇代碼容易識別的字符串。如果UIKit不能自動創(chuàng)建一個(gè)視圖控制器,要求你創(chuàng)建,UIKit會提供視圖控制器和其所有父視圖控制器的恢復(fù)標(biāo)示符。這一串的標(biāo)示符代表視圖控制器的恢復(fù)路徑,及如何決定哪些視圖控制器被請求。恢復(fù)路徑從根視圖控制器開始,包括呈現(xiàn)的每個(gè)視圖控制器和被請求的視圖控制器。
恢復(fù)標(biāo)示圖通常以視圖控制器類名命名。如果你在很多地方使用相同的類,你可能希望分配更有意義的值。例如,你可以基于視圖控制器管理的數(shù)據(jù)來指定字符串。
每個(gè)視圖控制器的恢復(fù)路徑必須是唯一的。如果一個(gè)容器視圖控制器有兩個(gè)子視圖,容器必須為每個(gè)子視圖控制器分配一個(gè)獨(dú)特的恢復(fù)標(biāo)示符。一些UIKit中容器視圖控制器會自動消除歧義子視圖控制器,允許為每個(gè)子視圖控制器使用相同恢復(fù)標(biāo)示符。例如,類根據(jù)每個(gè)子視圖控制器在導(dǎo)航堆棧中的位置添加信息。關(guān)于一個(gè)給定視圖控制器的行為更多信息,參見相應(yīng)的類引用。
如何使用恢復(fù)標(biāo)示符和恢復(fù)路徑創(chuàng)建視圖控制器的更多信息,參見啟動時(shí)恢復(fù)視圖控制器(Restoring View Controllers at Launch Time)。
不包括視圖控制器組
在恢復(fù)過程中不包括整個(gè)視圖控制器組,設(shè)置父視圖控制器的恢復(fù)標(biāo)示符為nil。圖7-1展示了設(shè)置恢復(fù)標(biāo)示符為nil對視圖層級結(jié)構(gòu)的影響。缺少保存數(shù)據(jù),在稍后無法恢復(fù)視圖控制器。
在隨后的恢復(fù)過程中,并不完全刪除其中不包括的一個(gè)或多個(gè)視圖控制器。在啟動時(shí),任何視圖控制器都是應(yīng)用的一部分,默認(rèn)創(chuàng)建設(shè)置,如圖7-2所示。按照默認(rèn)配置創(chuàng)建視圖控制器。
自動保存過程中,可以手動保存不包括的視圖控制器。保存視圖控制器的引用即保存視圖控制器和其狀態(tài)信息。例如,如果圖7-1中的應(yīng)用代理保存導(dǎo)航控制器的三個(gè)子視圖控制器,他們的狀態(tài)將被保存下來。在恢復(fù)過程中,應(yīng)用代理可以重現(xiàn)創(chuàng)建這些視圖控制器并push他們到導(dǎo)航控制器的堆棧上。
保存視圖控制器的視圖
一些視圖有更多與視圖而不是父視圖控制器相關(guān)的狀態(tài)信息。例如,你希望保存滾動視圖的一個(gè)滾動位置。視圖控制器負(fù)責(zé)提供滾動視圖的內(nèi)容,而滾動視圖本身負(fù)責(zé)保存其視覺狀態(tài)。
保存視圖狀態(tài),執(zhí)行以下操作:
· 指定為 restoration Identifier 屬性一個(gè)有效的字符串。
· 使用具有有效標(biāo)示符的視圖控制器的視圖。
· 對于表視圖和集合視圖,指定采用UI Data Source Model Association 協(xié)議的數(shù)據(jù)源
將標(biāo)示符分配給視圖告訴UIKit應(yīng)該保存視圖狀態(tài)。當(dāng)稍后恢復(fù)視圖控制器,UIKit也恢復(fù)有恢復(fù)標(biāo)示符的視圖的狀態(tài)。
在啟動時(shí)恢復(fù)視圖控制器
在啟動時(shí),UIKit試圖恢復(fù)你的應(yīng)用到之前的狀態(tài)。那時(shí),UIKit訪問你的應(yīng)用創(chuàng)建(或確定)用戶界面保存的視圖控制器對象。當(dāng)定位視圖控制器時(shí),UIKit按照以下順序搜索:
**如果視圖控制器有一個(gè)恢復(fù)類,UIKit訪問該類提供視圖控制器。UIKit調(diào)用恢復(fù)類的 [viewControllerWithRestorationIdentifierPath:coder:](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIViewControllerRestoration_protocol/index.html#//apple_ref/occ/intfcm/UIViewControllerRestoration/viewControllerWithRestorationIdentifierPath:coder:) 方法來檢索視圖控制器。**如果該方法返回nil,則假設(shè)應(yīng)用并不希望重新創(chuàng)建視圖控制器,UIKit停止尋找該視圖控制器。
**如果視圖控制器沒有恢復(fù)類,UIKit要求應(yīng)用代理提供視圖控制器。**UIKit調(diào)用應(yīng)用代理的[application:viewControllerWithRestorationIdentifierPath:coder:](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/index.html#//apple_ref/occ/intfm/UIApplicationDelegate/application:viewControllerWithRestorationIdentifierPath:coder:)方法尋找沒有恢復(fù)類的視圖控制器。如果該方法返回nil,UIKit嘗試找到隱藏的視圖控制器。
**如果有正確恢復(fù)路徑的視圖控制器已經(jīng)存在,UIKit使用該對象。**如果你的應(yīng)用在啟動時(shí)(無論是以編程的方式還是通過storyboard加載的方式)創(chuàng)建視圖控制器,并且這些視圖控制器有恢復(fù)標(biāo)識符,UIKit基于他們的恢復(fù)路徑隱式的查找他們。
**如果視圖控制器最初是從storyboard文件加載,UIKit使用保存的storyboard信息來定位并創(chuàng)建它。**UIKit保存視圖控制器的storyboard信息到恢復(fù)文件中。在恢復(fù)時(shí),UIKit使用這些信息來定位相同的storyboard文件,如果通過其他任何方式?jīng)]有找到視圖控制器,則實(shí)例化相應(yīng)視圖控制器。
將恢復(fù)類分配給視圖控制器可以防止UIKit隱式的搜索該視圖控制器。使用恢復(fù)類讓你可以控制是否真正創(chuàng)建一個(gè)視圖控制器。例如,如果你的類決定不再重新創(chuàng)建視圖控制器,viewControllerWithRestorationIdentifierPath:coder:
方法可以返回nil。當(dāng)不存在恢復(fù)類,UIKit會盡可能找到或者創(chuàng)建該視圖控制器來恢復(fù)它。
當(dāng)使用一個(gè)恢復(fù)類,viewControllerWithRestorationIdentifierPath:coder:
方法應(yīng)該創(chuàng)建該類的新實(shí)例,執(zhí)行初始化,并返回結(jié)果對象。列表7-1 展示了如何使用此方法從storyboard中加載視圖控制器的例子。因?yàn)樵撘晥D控制器最初從storyboard加載,該方法使用UIStateRestorationViewControllerStoryboardKey key從文件中找到storyboard。注意:該方法不配置視圖控制器的數(shù)據(jù)字段。當(dāng)解碼視圖控制器的狀態(tài)時(shí),發(fā)生這一步。
列表7-1 在恢復(fù)過程中創(chuàng)建一個(gè)新視圖控制器
<pre><code>
+(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;
}
</pre></code>
當(dāng)手動重新創(chuàng)建視圖控制器,重新分配恢復(fù)標(biāo)識符和恢復(fù)類是個(gè)好習(xí)慣。恢復(fù)恢復(fù)標(biāo)識符的最簡單的方法是獲取identifierComponents
數(shù)組中的最后一項(xiàng),并將其分配給你的視圖控制器。
在啟動時(shí)由應(yīng)用主要storyboard文件中創(chuàng)建的對象,不需要為每個(gè)對象創(chuàng)建新實(shí)例。讓UIKit隱式的查找這些對象或者使用應(yīng)用代理的application:viewControllerWithRestorationIdentifierPath:coder:
方法查找這些存在的對象。
編碼和解碼視圖控制器狀態(tài)
對于每個(gè)要保留的對象,UIKit調(diào)用對象的encodeRestorableStateWithCoder:方法來保存其狀態(tài)。在恢復(fù)過程中,UIKit調(diào)用對應(yīng)的decodeRestorableStateWithCoder: 方法來解碼該狀態(tài)并將其用于該對象。這些方法的實(shí)現(xiàn)是可選的,但是建議,實(shí)現(xiàn)。你可能使用它們來保存和恢復(fù)以下類型的信息:
· 顯示數(shù)據(jù)的引用(不是數(shù)據(jù)本身)
· 對于容器視圖控制器,其子視圖控制器的引用
· 當(dāng)前選擇的信息
· 對于用戶可配置視圖的視圖控制器,當(dāng)前配置視圖的信息
在你的編碼和解碼方法中,你可以編碼對象和編碼器支持的任何數(shù)據(jù)類型。除了視圖和視圖控制器的其他所有對象必須采用 NSCoding協(xié)議并使用協(xié)議中的方法來保存其狀態(tài)。對于視圖和視圖控制器,編碼器不使用 NSCoding協(xié)議來保存對象狀態(tài)。相反,編碼器保存該對象的恢復(fù)標(biāo)識符并將其添加到保存對象列表,這將導(dǎo)致該對象的encodeRestorableStateWithCoder:
方法被調(diào)用。
視圖控制器的encodeRestorableStateWithCoder:
和decodeRestorableStateWithCoder:
方法必須在實(shí)現(xiàn)的某個(gè)位置調(diào)用super方法。調(diào)用super方法讓父類有機(jī)會保存和恢復(fù)任何額外的信息。列表7-2展示了這些方法的實(shí)現(xiàn)例子,該方法保存一個(gè)數(shù)值用于識別指定視圖控制器。
列表7-2 編碼和界面視圖控制器狀態(tài)
<pre><code>
-(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];
}
</pre></code>
在編碼和解碼過程中,編碼對象不共享。每個(gè)保存狀態(tài)的對象接收自己的編碼對象。使用獨(dú)特的編碼器表明你不用擔(dān)心key之間的命名沖突。然而,不要使用UIApplicationStateRestorationBundleVersionKey,UIApplicationStateRestorationUserInterfaceIdiomKey和UIStateRestorationViewControllerStoryboardKey key來命名。UIKit使用這些key來存儲視圖控制器狀態(tài)的額外信息。
關(guān)于視圖控制器編碼解碼方法的更多信息,參見UIViewcontroller類引用(UIViewController Class Reference)。
保存和恢復(fù)視圖控制器技巧
在視圖控制器中添加狀態(tài)保存與恢復(fù),考慮以下指南:
· 請記住可能不希望保存所有視圖控制器。在某些情況下,保存某個(gè)視圖控制器沒有意義。例如,如果應(yīng)用顯示一個(gè)變更,你希望取消操作并恢復(fù)到應(yīng)用的前一個(gè)界面。在這種情況下,你不用保存該視圖控制器。
· 恢復(fù)過程中避免交換視圖控制器類。狀態(tài)保存系統(tǒng)為其保存的視圖控制器類編碼。在恢復(fù)期間,如果你的應(yīng)用返回一個(gè)不匹配原始對象(或不是原始對象的子類)的對象,系統(tǒng)不要求視圖控制器解碼任何狀態(tài)信息。因此,劃掉舊的視圖控制器換成完全不同的視圖控制器,這個(gè)過程不恢復(fù)該對象的全部狀態(tài)。
· 狀態(tài)保存系統(tǒng)希望你有意的使用視圖控制器。恢復(fù)過程依賴視圖控制器的控制關(guān)系來重建你的界面。如果你沒有正確使用容器視圖控制器,保存系統(tǒng)不能找到你的視圖控制器。例如,除非相應(yīng)的視圖控制器之間包含某種關(guān)系,否則不要在不同的視圖中嵌入一個(gè)視圖控制器的視圖。