子類化視圖控制器
設計UI
可以使用Xcode中的storyboard文件直觀地定義視圖控制器的UI,也可以以編程方式來創(chuàng)建UI涩笤。但storyboard可以將視圖的內容可視化年柠,并根據(jù)需要自定義視圖層次結構匆浙。以可視化方式構建UI界面中燥,可以讓我們快速進行更改并能看到結果锄奢,而無需構建和運行應用程序。
下圖顯示了一個storyboard的例子靡狞。每個矩形區(qū)域代表一個視圖控制器及其相關視圖耻警,視圖控制器之間的箭頭是視圖控制器的關系和節(jié)點隔嫡。關系將容器視圖控制器連接到其子視圖控制器甸怕。Segue可用于在界面中的視圖控制器之間導航。
處理用戶交互
應用程序的響應者對象會處理傳入的事件腮恩,雖然視圖控制器是響應者對象梢杭,但它們很少直接處理觸摸事件。相反秸滴,視圖控制器通常以下列方式處理事件:
- 視圖控制器定義了處理更高級別事件的操作方法武契。行動方法回應:
- 具體行動〉春控件和一些視圖調用行動方法來報告特定的交互咒唆。
- 手勢識別器。手勢識別器調用行動方法來報告手勢的當前狀態(tài)释液。使用視圖控制器處理狀態(tài)變更或響應已完成的手勢全释。
- 視圖控制器觀察由系統(tǒng)或其他對象發(fā)出的通知,通知報告更改误债。通知也是視圖控制器更新其狀態(tài)的一種方式浸船。
- 視圖控制器充當另一個對象的數(shù)據(jù)源或委托。例如寝蹈,可以將它們用作
CLLocationManager
對象的代理李命,該對象將更新的位置發(fā)送給其委托對象。
在運行時顯示視圖
storyboard加載和顯示視圖控制器視圖的過程非常簡單箫老。當需要時封字,UIKit會自動從stroyboard文件中加載視圖。作為加載過程的一部分,UIKit執(zhí)行以下任務序列:
- 使用storyboard文件中的信息實例化視圖周叮。
- 連接所有的outlets和actions辩撑。
- 將根視圖分配給視圖控制器的視圖屬性。
- 調用視圖控制器的
awakeFromNib
方法仿耽。調用此方法時合冀,視圖控制器的特征集合為空,視圖可能不在其最終位置项贺。 - 調用視圖控制器的
viewDidLoad
方法君躺。在此方法中添加或者刪除視圖,修改布局約束开缎,并未視圖加載數(shù)據(jù)棕叫。
在屏幕上顯示視圖控制器的視圖之前,UIKit提供了額外的機會在視圖顯示前后來執(zhí)行需要的操作奕删。具體來說俺泣,UIKit執(zhí)行以下任務序列:
- 在視圖即將出現(xiàn)在屏幕上前,調用視圖控制器的
viewWillAppear:
方法完残。 - 更新視圖的布局伏钠。
- 在屏幕上顯示視圖。
- 視圖顯示后谨设,調用視圖控制器的
viewDidAppear:
方法熟掂。
添加、刪除或修改視圖的尺寸或者位置時扎拣,也要添加和刪除適用于這些視圖的任何約束赴肚。對視圖層次結構進行與布局相關的更改會導致UIKit將布局標記為臟。在下一個更新周期中二蓝,布局引擎使用當前布局約束條件計算視圖的尺寸和位置誉券,并將這些更改應用于視圖層次結構。
管理視圖布局
當視圖的尺寸和位置發(fā)生變化時刊愚,UIKit將更新視圖層次結構的布局信息踊跟。對于使用Auto Layout配置的視圖,UIKit將使用Auto Layout引擎根據(jù)當前約束來更新布局百拓。UIKit還會通知其他感興趣的對象(如正在呈現(xiàn)的控制器)布局發(fā)生更改琴锭,以便它們可以做出相應的響應。
在布局過程中衙传,UIKit會在幾個時間點發(fā)出通知决帖,以便我們可以執(zhí)行其他與布局相關的任務。使用這些用紙來修改布局約束蓖捶,或者在應用布局約束后對布局進行最終的調整地回。在布局過程中,UIKit為每個受影響的視圖控制器執(zhí)行以下操作:
- 根據(jù)需要更新視圖控制器及其視圖的特征集合。
- 調用視圖控制器的
viewWillLayoutSubviews
方法刻像。 - 調用當前
UIPresentationController
的containerViewWillLayoutSubviews
方法畅买。 - 調用視圖控制器的根視圖的
layoutSubviews
方法。此方法默認使用可用的約束來計算新的布局信息细睡。然后該方法遍歷視圖層谷羞,并調用每個子視圖的layoutSubviews
方法。 - 將計算的布局信息應用于視圖溜徙。
- 調用視圖控制器的
viewDidLayoutSubviews
方法湃缎。 - 調用當前
UIPresentationController
對象的containerViewDidLayoutSubviews
方法。
視圖控制器可以使用viewWillLayoutSubviews
和viewDidLayoutSubviews
方法來執(zhí)行可能影響布局過程的附加更新蠢壹。在布局之前嗓违,可以添加或刪除視圖,更新視圖的尺寸或位置图贸,更新約束或更新其他視圖相關的屬性蹂季。布局之后,可以重新加載表格數(shù)據(jù)疏日,更新其他視圖的內容偿洁,或對視圖的尺寸和位置進行最終調整。
實現(xiàn)一個容器視圖控制器
容器視圖控制器是將多個視圖控制器的內容合并到單個用戶界面中的一種方法制恍。容器視圖控制器通常使導航變得容易父能,并基于現(xiàn)有內容創(chuàng)建新的用戶界面類型神凑。UIKit中容器視圖控制器的示例包括UINavigationController
净神、UITabBarController
和UISplitViewController
,它們都可以方便在用戶界面的不同部分之間進行導航溉委。
設計自定義容器視圖控制器
幾乎在任何情況下鹃唯,容器視圖控制器都像其他任何內容視圖控制器一樣管理根視圖和一些內容。區(qū)別在于容器視圖控制器從其他視圖控制器獲取其內容的一部分瓣喊。其獲取的內容僅限于其他視圖控制器的視圖坡慌,這些視圖嵌入在其自己的根視圖層次結構中。容器視圖控制器設置任何嵌入視圖的尺寸和位置藻三,但原始視圖控制器仍然管理這些視圖內的內容洪橘。
在設計容器視圖控制器時,需要始終了解容器和包含的視圖控制器之間的關系棵帽。視圖控制器的關系可以幫助告知它們的內容應該如何顯示在屏幕上熄求,以及容器如何在內部管理它們。在設計過程中逗概,需要搞清楚以下幾個問題:
- 容器的作用是什么弟晚?其子視圖控制器扮演什么樣的角色?
- 多少個子視圖控制器同時顯示?
- 同級子視圖控制器之間有什么關系(如果有的話)卿城?
- 子視圖控制器如何添加到容器或從容器中移除枚钓?
- 子視圖控制器的尺寸和位置能改變嗎?這些變化在什么情況下發(fā)生瑟押?
- 容器是否提供任何裝飾或導航相關的視圖搀捷?
- 容器視圖控制器和子視圖控制器之間如何通信?容器是否需要向
UIViewController
類定義的標準事件報告特定的事件多望? - 容器的外觀是否可以用不同的方式配置指煎?如果可以镇饺,如何實現(xiàn)鸽斟?
在定義了各種對象的角色之后,容器視圖控制器的實現(xiàn)相對簡單致扯。UIKit唯一的要求就是在容器視圖控制器和任何子視圖控制器之間建立正式的父子關系枢纠。父子關系確保子視圖控制器收到任何相關的系統(tǒng)消息像街。除此之外,大部分的實際工作都是在包含視圖的布局和管理過程中發(fā)生的晋渺,每個容器都是不同的镰绎。我們可以將視圖放置在容器的內容區(qū)域的任何位置,然后根據(jù)需要調整視圖的尺寸木西。還可以將自定義視圖添加到視圖層次結構中畴栖,以提供裝飾或者輔助導航。
示例:導航控制器
UINavigationController
對象支持通過分層數(shù)據(jù)集來導航八千。導航界面一次顯示一個子視圖控制器吗讶。界面頂部的導航欄顯示數(shù)據(jù)層次結構中當前位置,并顯示后退按鈕以向后移動一個級別恋捆。向下導航到另一個子視圖控制器照皆,并將子視圖控制器添加到數(shù)據(jù)層次結構中。
視圖控制器之間的導航由導航控制器及其子視圖控制器共同管理沸停。當用戶與子視圖控制器的按鈕或表格進行交互后膜毁,子視圖控制器要求導航控制器將新的視圖控制器推入視圖。子視圖控制器處理新的視圖控制器的內容的配置愤钾,但導航控制器管理轉場動畫瘟滨。導航控制器還管理導航欄,該導航欄顯示用于解除最頂層視圖控制器的后退按鈕能颁。
下圖顯示了導航控制器及其視圖的結構杂瘸。大多數(shù)內容區(qū)域由最頂層的子視圖控制器填充,只有一小部分被導航欄占用劲装。
在緊湊和常規(guī)的環(huán)境下胧沫,導航控制器一次只顯示一個子視圖控制器昌简。導航控制器調整其子視圖控制器以適應可用空間。
示例:分割控制器
UISplitViewController
對象以主要-細節(jié)排列方式來顯示兩個視圖控制器的內容绒怨。在這種排列中纯赎,一個視圖控制器(主視圖)的內容決定了其他視圖控制器顯示的細節(jié)。兩個視圖控制器的可見性是可配置的南蹂,但也受到當前環(huán)境的支配犬金。在規(guī)則的水平環(huán)境中,分割視圖控制器可以同時顯示兩個子視圖控制器六剥,或者可以隱藏主視圖控制器并根據(jù)需要顯示晚顷。在緊湊的環(huán)境中,分割視圖控制器一次只顯示一個視圖控制器疗疟。
下圖顯示了在一個常規(guī)的水平環(huán)境中的分割視圖界面及其視圖的結構该默。分割視圖控制器本身只有默認的容器視圖。在這個例子中策彤,兩個子視圖是并排顯示的栓袖。子視圖的尺寸是可配置的,主視圖的可見性也是可配置的店诗。
在Interface Builder中配置容器
要在設計時創(chuàng)建父子容器關系裹刮,需要將一個容器視圖對象添加到視圖控制器中,如下圖所示庞瘸。容器視圖對象是代表子視圖控制器內容的占位符對象捧弃。使用該視圖來調整和定位與容器中其他視圖相關的子視圖。
當使用一個或多個容器視圖加載視圖控制器時擦囊,Interface Builder還會加載與這些視圖關聯(lián)的子視圖控制器违霞。子視圖控制器必須與父視圖控制器同時實例化,以便建立適當?shù)母缸雨P系霜第。
如果不使用Interface Builder來設置父 - 子容器關系葛家,則必須通過將每個子項添加到容器視圖控制器來以編程方式創(chuàng)建這些關系户辞。
實現(xiàn)自定義容器視圖控制器
要實現(xiàn)一個容器視圖控制器泌类,必須建立容器視圖控制器和其子視圖控制器之間的關系。在嘗試管理任何子視圖控制器的視圖之前底燎,建立這些父子關系是必需的刃榨。這樣做讓UIKit知道容器視圖控制器正在管理子視圖控制器的尺寸和位置。我們可以在Interface Builder中創(chuàng)建這些關系双仍,或以編程方式創(chuàng)建它們枢希。
將子視圖控制器添加到內容
要以編程方式將子視圖控制器合并到內容中,請執(zhí)行以下操作朱沃,在相關的視圖控制器之間創(chuàng)建父子關系:
- 調用容器視圖控制器的
addChildViewController:
方法苞轿,此方法告訴UIKit容器視圖控制器現(xiàn)在正在管理子視圖控制器的視圖茅诱。 - 設置好子視圖控制器內容的尺寸和位置,將子視圖控制器的根視圖添加到容器視圖控制器的視圖層次結構中搬卒。
- 添加任何約束來管理子視圖控制器的根視圖的尺寸和位置瑟俭。
- 調用子視圖控制器的
didMoveToParentViewController:
方法。
以下代碼展示了如何在容器視圖控制器中嵌入一個子視圖控制器契邀。建立父子關系后摆寄,容器視圖控制器設置其子視圖控制器內容的框架,并將子視圖控制器的根視圖添加到自己的視圖層次結構中坯门。設置子視圖控制器的根視圖的尺寸很重要微饥,能夠確保視圖在容器中正確顯示。在添加視圖之后古戴,容器視圖控制器調用子視圖控制器的didMoveToParentViewController:
方法欠橘,以使子視圖控制器有機會響應視圖所有權的更改。
- (void) displayContentController: (UIViewController*) content {
[self addChildViewController:content];
content.view.frame = [self frameForContentController];
[self.view addSubview:self.currentClientView];
[content didMoveToParentViewController:self];
}
注意现恼,在上面的例子中简软,只調用了子視圖控制器的didMoveToParentViewController:
方法。容器視圖控制器的addChildViewController:
方法只會調用子視圖控制器的willMoveToParentViewController:
方法述暂,我們必須自己手動調用子視圖控制器的didMoveToParentViewController:
痹升。因為只有在將子視圖嵌入容器的視圖層次結構中后,才能調用此方法畦韭。
使用自動布局時疼蛾,在將子對象添加到容器的視圖層次結構后,在容器和子對象之間設置約束艺配。添加的約束只會影響子視圖控制器的根視圖的尺寸和位置察郁。請勿直接更改子視圖層次結構中的根視圖或任何其他視圖的內容。
移除子視圖控制器
要從容器視圖控制器中刪除子視圖控制器转唉,可以通過執(zhí)行以下操作來刪除視圖控制器之間的父子關系:
- 調用子視圖控制器的
willMoveToParentViewController:
方法且傳入的值為nil
皮钠。 - 刪除為子視圖控制器的根視圖配置的約束。
- 從容器視圖控制器的視圖層中移除該子視圖控制器的根視圖赠法。
- 調用子視圖控制器的
removeFromParentViewController
方法來結束父子關系麦轰。
刪除子視圖控制器會永久切斷容器視圖控制器與子視圖控制器之間的關系。只有當不再需要引用子視圖控制器時砖织,才能移除子視圖控制器款侵。例如,當一個新視圖控制器推入導航堆棧時侧纯,導航控制器并不會移除當前的子視圖控制器新锈。只有當它們從堆棧中彈出時,才會被刪除眶熬。
以下代碼顯示了如何從容器視圖控制器中刪除子視圖控制器妹笆。調用子視圖控制器的willMoveToParentViewController:
方法且傳入nil
值為子視圖控制器提供了為更改做準備的機會块请。子視圖控制器的removeFromParentViewController
方法還會調用其didMoveToParentViewController:
方法其傳入nil
值。將容器視圖控制器設置為nil
拳缠,也會從容器中刪除子視圖负乡。
- (void) hideContentController: (UIViewController*) content {
[content willMoveToParentViewController:nil];
[content.view removeFromSuperview];
[content removeFromParentViewController];
}
子視圖控制器之間的轉場動畫
當需要用一個子視圖控制器動畫替換另一個子視圖控制器時,可以將子視圖控制器的添加和刪除合并到轉場動畫過程中脊凰。在執(zhí)行動畫前抖棘,請確保兩個子視圖控制器都是容器視圖控制器內容的一部分,但是讓當前的子視圖控制器知道它即將被移除狸涌。在動畫過程中切省,將新子視圖控制器的視圖移動到位并移除舊子視圖控制器的視圖。動畫完成后帕胆,移除舊子視圖控制器朝捆。
以下代碼顯示了如何使用轉場動畫將一個子視圖控制器替換成另一個子視圖控制器的示例。在這個示例中懒豹,transitionFromViewController:toViewController:duration:options:animations:completion:
方法會自動更新容器視圖控制器的視圖層次機構芙盘,不需要我們自己手動添加和刪除視圖。
- (void)cycleFromViewController:(UIViewController*)oldVC toViewController:(UIViewController*)newVC {
// Prepare the two view controllers for the change.
[oldVC willMoveToParentViewController:nil];
[self addChildViewController:newVC];
// Get the start frame of the new view controller and the end frame
// for the old view controller. Both rectangles are offscreen.
newVC.view.frame = [self newViewStartFrame];
CGRect endFrame = [self oldViewEndFrame];
// Queue up the transition animation.
[self transitionFromViewController: oldVC toViewController: newVC
duration: 0.25 options:0
animations:^{
// Animate the views to their final positions.
newVC.view.frame = oldVC.view.frame;
oldVC.view.frame = endFrame;
}completion:^(BOOL finished) {
// Remove the old view controller and send the final
// notification to the new view controller.
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:self];
}];
}
管理子視圖控制器的外觀更新
在將子視圖控制器添加到容器視圖控制器后脸秽,容器視圖控制器會自動將外觀相關的消息轉發(fā)給子視圖控制器儒老。大多數(shù)情況下,這樣能確保所有事件都正確發(fā)生记餐。但是驮樊,有時默認行為可能會以無意義的順序發(fā)送這些事件。例如片酝,如果多個子視圖控制器同時改變其視圖狀態(tài)囚衔,則可能需要合并這些更改,以使外觀回調都以更合理的順序同時發(fā)生雕沿。
要接管外觀回調的責任练湿,需要覆寫容器視圖控制器的shouldAutomaticallyForwardAppearanceMethods
方法并返回NO
。
- (BOOL) shouldAutomaticallyForwardAppearanceMethods {
return NO;
}
當有轉場動畫發(fā)生時审轮,根據(jù)需要調用子視圖控制器的beginAppearanceTransition:animated:
或者endAppearanceTransition
方法肥哎。
-(void) viewWillAppear:(BOOL)animated {
[self.child beginAppearanceTransition: YES animated: animated];
}
-(void) viewDidAppear:(BOOL)animated {
[self.child endAppearanceTransition];
}
-(void) viewWillDisappear:(BOOL)animated {
[self.child beginAppearanceTransition: NO animated: animated];
}
-(void) viewDidDisappear:(BOOL)animated {
[self.child endAppearanceTransition];
}
自定義容器視圖控制器的幾點建議
設計、開發(fā)和測試新的容器視圖控制器需要時間断国。雖然每個視圖控制器的行為是直截了當?shù)南湍罚w控制起來可能相當復雜。在自定義容器控制器時稳衬,請考慮以下提示:
- 只訪問子視圖控制器的根視圖。容器視圖控制器只能訪問每個子視圖控制器的根視圖坐漏,也就是子視圖控制器的
view
屬性薄疚,它不應該訪問任何子視圖控制器的其他視圖碧信。 - 子視圖控制器應該對其容器視圖控制器有最少的了解。子視圖控制器應該關注自己的內容街夭。如果容器視圖控制器允許其行為受到子視圖控制器的影響砰碴,則應該使用委托設計模式來管理這些交互。
- 首選使用常規(guī)視圖來設計容器視圖控制器板丽。使用常規(guī)視圖(而不是來自子視圖控制器的視圖)使我們有機會在簡單的環(huán)境中測試布局約束和轉場動畫呈枉。當常規(guī)視圖能按照預期工作時,再將常規(guī)視圖替換成子視圖控制器的視圖埃碱。
將控制委派給子視圖控制器
容器視圖控制器可以將其自身外觀的某些方面委托給其一個或多個子視圖控制器猖辫。可以通過以下方式委派控制權:
- 讓一個子視圖控制器確定狀態(tài)欄的樣式砚殿。要將狀態(tài)欄外觀委托給子視圖控制器啃憎,需要覆寫容器視圖控制器的
childViewControllerForStatusBarStyle
和childViewControllerForStatusBarHidden
方法中的一個或者兩個。 - 讓子視圖控制器指定自己的尺寸似炎。具有靈活布局的容器視圖控制器可以使用其子視圖控制器的
preferredContentSize
屬性來幫助確定子視圖控制器的尺寸辛萍。
支持輔助功能
iOS系統(tǒng)為了幫助盲人進行人機交互,設計了VoiceOver讀屏技術羡藐。VoiceOver能夠讀出屏幕上的信息贩毕,其屬于輔助功能的一部分。有關讓UIViewController
支持輔助功能的詳細信息仆嗦,請參看Supporting Accessibility耳幢。
保存和恢復狀態(tài)
視圖控制器在應用程序狀態(tài)保存和恢復過程中起著重要作用。狀態(tài)保存會在應用程序被掛起之前保存其當前配置欧啤,以便后續(xù)應用程序啟動時恢復之前的配置睛藻。將應用程序恢復到以前的配置為用戶節(jié)省來時間,并提供來更好的用戶體驗邢隧。
保存和恢復過程大都是自動的店印,但是需要我們告知系統(tǒng)應用程序的哪些部分要保存。保存應用程序的視圖控制器的步驟如下:
- (必需)將恢復標識符分配給要保留其配置的視圖控制器倒慧。
- (必需)告訴系統(tǒng)如何在啟動時創(chuàng)建或定位新的視圖控制器對象按摘。
- (可選)對于每個視圖控制器,存儲能將視圖控制器返回到其之前配置所需的任何特定配置數(shù)據(jù)纫谅。
有關保存和恢復過程的概述炫贤,可以參看App Programming Guide for iOS。
標記需要保存的視圖控制器
UIKit只會保存被標記需要保存的視圖控制器付秕。每個視圖控制器都有一個restorationIdentifier
屬性兰珍,其默認值為nil
,為該屬性設置有效的字符串值將告訴UIKit應該保存視圖控制器及其視圖询吴。我們可以以編程方式或在storyboard中為視圖控制器分配恢復標識符掠河。
分配恢復標識符時亮元,一定要記住,當前視圖控制器層次結構中的所有父視圖控制器也必須具有恢復標識符唠摹。在保存過程中爆捞,UIKit從window的根視圖控制器開始,遍歷當前視圖控制器層次結構勾拉。如果該層次結構中的視圖控制器沒有恢復標識符煮甥,則視圖控制器及其所有子視圖控制器和呈現(xiàn)的視圖控制器都將被忽略。
選擇有效的恢復標識符
UIKit會使用我們分配的恢復標識符去重新創(chuàng)建視圖控制器藕赞,所以需要選擇容易被代碼識別的字符串來作為恢復標識符成肘。如果UIKit無法自動創(chuàng)建一個視圖控制器,其會要求我們自己手動創(chuàng)建找默,并為我們提供視圖控制器及其所有父視圖控制器的恢復標識符艇劫。這個標識符鏈標表示視圖控制器的恢復路徑,以及如何確定正在請求哪個視圖控制器惩激〉晟罚恢復路徑從根視圖控制器開始,直至所請求的視圖控制器风钻。
恢復標識符通常是視圖控制器的類名顷蟀。如果在許多地方使用相同的類,則可能需要分配更有意義的值骡技。例如鸣个,可以根據(jù)視圖控制器管理的數(shù)據(jù)分配一個字符串。
每個視圖控制器的恢復路徑必須是唯一的布朦。如果容器視圖控制器有兩個子視圖控制器囤萤,容器視圖控制器必須為每個子視圖控制器分配一個唯一的恢復標識符。UIKit中的一些容器視圖控制器會自動消除其子視圖控制器的歧義是趴,使得我們可以為每個子視圖控制器使用相同的恢復標識符涛舍。例如,UINavigationController
類會根據(jù)其子視圖控制器在導航堆棧中的位置給其子視圖控制器添加信息唆途。
排除視圖控制器組
要在恢復過程中排除整個視圖控制器組富雅,請將父視圖控制器的恢復標識符設置為nil
。下圖顯示了將視圖控制器層次結構中的視圖控制器的恢復標識符設為nil
的影響肛搬。
排除一個或者多個視圖控制器并不會在隨后的恢復過程中完全移除這些視圖控制器没佑。在應用程序啟動時,任何視圖控制器都是應用程序的默認配置的一部分温赔,它們仍然會被創(chuàng)建蛤奢,如下圖所示。
在自動保存過程中排除視圖控制器并不會阻止我們去手動保存它。在恢復歸檔中远剩,保存對視圖控制器的引用可以保留視圖控制器及其狀態(tài)信息扣溺。例如骇窍,如果圖7-1中的應用程序委托保存了導航控制器的三個子項瓜晤,則它們的狀態(tài)將被保留。在還原期間腹纳,應用程序委托可以重新創(chuàng)建這些視圖控制器并將其推送到導航控制器的堆棧中痢掠。
保存視圖控制器的視圖
一些視圖具有與視圖相關的附加狀態(tài)信息,例如我們可能想保存滾動視圖的滾動位置嘲恍。當視圖控制器負責提供滾動視圖的內容時足画,滾動視圖本身負責保持其視覺狀態(tài)。
要保存視圖的狀態(tài)佃牛,需要執(zhí)行以下操作:
- 為視圖的
restorationIdentifier
屬性分配一個有效的字符串淹辞。 - 使用也具有有效的恢復標識符的視圖控制器中的視圖。
- 對于表視圖和集合視圖俘侠,分配一個遵循
UIDataSourceModelAssociation
協(xié)議的數(shù)據(jù)源對象象缀。
為視圖分配一個恢復標識符將告知UIKit應該將視圖的狀態(tài)寫入恢復歸檔,當視圖控制器稍后被恢復時爷速,UIKit還恢復具有恢復標識符的任何視圖的狀態(tài)央星。
在應用程序啟動時恢復視圖控制器
在應用程序啟動時,UIKit會嘗試將應用程序恢復到之前的狀態(tài)惫东。此時莉给,UIKit會要求應用程序創(chuàng)建(或定位)包含之前保存的用戶界面的視圖控制器。UIKit在嘗試定位視圖控制器時廉沮,會按照以下順序去搜索:
- 如果視圖控制器有恢復類颓遏,UIkit會要求該類提供視圖控制器。UIKit會調用關聯(lián)的恢復類的
viewControllerWithRestorationIdentifierPath:coder:
方法來檢索視圖控制器滞时。如果該方法返回nil
叁幢,UIkit會假定應用程序不用重新創(chuàng)建視圖控制器并且會停止查找。 - 如果視圖控制器沒有關聯(lián)恢復類漂洋,UIKit會要求應用程序委托提供視圖控制器遥皂。UIKit調用應用程序委托的
application:viewControllerWithRestorationIdentifierPath:coder:
方法來查找沒有恢復類的視圖控制器。如果該方法返回nil
刽漂,UIKit將嘗試隱式查找視圖控制器演训。 - 如果具有正確恢復路徑的視圖控制器已經存在,UIKit就會使用該視圖控制器對象贝咙。如果應用程序在啟動時創(chuàng)建視圖控制器(以編程方式或者從storyboard中加載)样悟,且視圖控制器具有恢復標識符,則UIKit會根據(jù)其恢復路徑隱式查找它們。
- 如果視圖控制器最初是從storyboarrd中加載的窟她,則UIKit使用保存的storyboard信息來定位和創(chuàng)建它陈症。UIKit將有關視圖控制器的storyboard信息保存在恢復歸檔中,在恢復時震糖,UIKit使用該信息來定位相同的storyboard文件录肯,并且會在使用任何其他方式都沒有查找到視圖控制器的情況下實例化相應的視圖控制器。
為視圖控制器分配一個恢復類可以避免UIKit隱式地檢索該視圖控制器吊说。使用恢復類可以更好地控制是否真的要創(chuàng)建視圖控制器论咏。例如,如果恢復類確定不應該重新創(chuàng)建視圖控制器颁井,則viewControllerWithRestorationIdentifierPath:coder:
方法可以返回nil
厅贪。當沒有恢復類時,UIKit會盡其所能找到或者創(chuàng)建視圖控制器雅宾,并將其恢復养涮。
當使用恢復類時,viewControllerWithRestorationIdentifierPath:coder:
方法應該創(chuàng)建一個類的新實例對象眉抬,執(zhí)行最低限度的初始化贯吓,并返回結果對象。以下代碼展示了一個如何使用此方法從storyboard中加載視圖控制器的例子吐辙。由于視圖控制器最初是從storyboard加載的宣决,因此此方法使用UIStateRestorationViewControllerStoryboardKey
鍵值從存檔中獲取storyboard。注意昏苏,此方法不會嘗試配置視圖控制器的數(shù)據(jù)內容尊沸。當視圖控制器的狀態(tài)被解碼后,才會去配置視圖控制器的數(shù)據(jù)內容贤惯。
+ (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;
}
在手動重新創(chuàng)建視圖控制器時洼专,重新分配恢復標識符和恢復類是一個好習慣》豕梗恢復恢復標識符的最簡單方法是獲取identifierComponents
數(shù)組中的最后一項屁商,并將其分配給視圖控制器。
對于在應用程序啟動時從main storyboard文件中創(chuàng)建的對象颈墅,請勿為每個對象創(chuàng)建新的實例蜡镶。請讓UIKit隱式查找這些對象,或者使用應用程序委托對象的application:viewControllerWithRestorationIdentifierPath:coder:
方法來查找現(xiàn)有對象恤筛。
編碼和解碼視圖控制器的狀態(tài)
對于每個要保存的對象官还,UIKit都會調用對象的encodeRestorableStateWithCoder:
方法,使其有機會保存其狀態(tài)毒坛。在恢復過程中望伦,UIKit會調用對應的decodeRestorableStateWithCoder:
方法來解碼該狀態(tài)并將其用于對象林说。對于視圖控制器,這些方法的實現(xiàn)是可選的屯伞,但建議實現(xiàn)腿箩。可以使用它們來保存和恢復以下類型的信息:
- 關聯(lián)引用所顯示的任何數(shù)據(jù)(不是數(shù)據(jù)本身)劣摇。
- 對于容器視圖控制器珠移,關聯(lián)引用其子視圖控制器。
- 與當前選擇有關的信息饵撑。
- 對于具有用戶可配置視圖的視圖控制器剑梳,提供關于該視圖當前配置的信息唆貌。
在編碼和解碼方法中滑潘,可以對編碼器支持的對象和任何數(shù)據(jù)類型進行編碼。對于除視圖和視圖控制器以外的所有對象锨咙,對象必須遵循NSCoding
協(xié)議语卤,并使用該協(xié)議的方法來寫入其狀態(tài)。對于視圖和視圖控制器酪刀,編碼器并不使用NSCoding
協(xié)議來保存對象的狀態(tài)粹舵。取而代之的是,編碼器保存對象的恢復標識符并將其添加到可保存對象的列表中骂倘,這會導致對象的encodeRestorableStateWithCoder:
方法被調用眼滤。
在實現(xiàn)視圖控制器的encodeRestorableStateWithCoder:
和decodeRestorableStateWithCoder:
方法時,必須調用super
的該方法历涝。調用super
的該方法讓父類有機會保存和恢復任何附加信息诅需。以下代碼展示來這些方法的一個示例實現(xiàn),它們保存用于標識指定視圖控制器的數(shù)字值荧库。
- (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];
}
編碼器對象在編碼和解碼過程并不共享堰塌。每個具有可保存狀態(tài)的對象都會接收到自己的編碼器對象。使用各自的編碼器意味著我們不用擔心密鑰之間的命名空間沖突分衫。但是场刑,請不要使用UIApplicationStateRestorationBundleVersionKey
、UIApplicationStateRestorationUserInterfaceIdiomKey
和UIStateRestorationViewControllerStoryboardKey
鍵名稱蚪战。UIKit使用這些鍵來存儲關于視圖控制器狀態(tài)的附加信息牵现。
有關實現(xiàn)視圖控制器的編碼和解碼的更多信息,請參看UIViewController Class Reference邀桑。
保存和恢復視圖控制器的幾點建議
在視圖控制器中添加對狀態(tài)保存和恢復的支持時瞎疼,請考慮以下準則:
- 請記住,我們可能不想保留所有視圖控制器概漱。在某些情況下丑慎,保留視圖控制器可能沒有意義。例如,如果應用程序正在顯示更改竿裂,則可能需要取消操作并將應用程序還原到上一個屏幕玉吁。在這種情況下,我們不需要保留要求輸入新密碼信息的視圖控制器腻异。
- 避免在恢復過程中換掉視圖控制器對象所屬的類进副。狀態(tài)保存系統(tǒng)對它保存的視圖控制器的類進行進行編碼。在恢復過程中悔常,如果應用程序返回的對象的類不匹配(或者不是原始對象的子類)影斑,則系統(tǒng)不會要求視圖控制器解碼任何狀態(tài)信息。
- 狀態(tài)保存系統(tǒng)希望我們按照預期使用視圖控制器机打〗没В恢復過程依賴于視圖控制器的包含關系來重建界面。如果我們沒有正確使用容器視圖控制器残邀,保存系統(tǒng)將找不到視圖控制器皆辽。例如,除非在相應的視圖控制器之間存在包含關系芥挣,否則不要將視圖控制器的視圖嵌入到不同的視圖中驱闷。