翻譯自“View Controller Programming Guide for iOS”。
1 彈出視圖控制器
在屏幕上顯示視圖控制器有兩種方式:嵌入容器視圖控制器或者彈出視圖控制器。容器視圖控制器提供了應用程序的主要導航,但彈出視圖控制器也是一個重要的導航工具。可以在當前視圖控制器上直接顯示一個新的視圖控制器。實現模態界面時,通常使用彈出視圖控制器,但也可以用于其它目的。
UIViewController類內置支持彈出視圖控制器,可用于所有視圖控制器對象。可以從任何視圖控制器彈出視圖控制器,但UIKit可能重路由該請求到另一個視圖控制器。彈出一個視圖控制器會在原始試圖控制器之間建立關系,原始視圖控制器稱為presenting視圖控制器,顯示的新視圖控制器稱為presented視圖控制器。這種關系是視圖控制器層級結構的一部分,會一直存在直到presented視圖控制器消失。
1.1 彈出和過渡過程
彈出視圖控制器是一種快速和簡單的方式,可以在屏幕上動畫的顯示新內容。UIKit內置的彈出機制可以使用內置或自定義動畫顯示新的視圖控制器。因為UIKit處理了所有工作,所以內置彈出和動畫只需要編寫很少代碼。也可以做些額外工作創建自定義彈出和動畫,然后在任何視圖控制器中使用它們。
可以通過代碼或segue發起視圖控制器的彈出。如果設計時知道應用程序的導航方式,segue是最簡單的方式。對于動態界面,或者沒有可用控件發起segue時,使用UIViewController的方法彈出視圖控制器。
1.1.1 彈出風格
視圖控制器的彈出風格決定了它在屏幕上的外觀。UIKit定義了很多標準彈出風格,每一種都有特定的外觀和意圖。也可以定義自己的彈出風格。設計應用程序時,選擇最合理的彈出風格,并為想要彈出的視圖控制器的modalPresentationStyle屬性指定適當的常量。
1.1.1.1 全屏(Full-Screen)彈出風格
全屏彈出風格覆蓋整個屏幕,并阻止與下面內容的交互。在水平方向為常規的環境中,只有其中一種全屏風格完全覆蓋下面的內容。其余的風格包含模糊的視圖或透明度,允許下面的視圖控制器部分顯示。在水平方向為緊湊的環境中,全屏彈出自動適應為UIModalPresentationFullScreen風格,并覆蓋下面所有內容。
圖8-1展示了水平方向為常規的環境中,UIModalPresentationFullScreen,UIModalPresentationPageSheet和UIModalPresentationFormSheet風格的彈出外觀。圖中,左上角的綠色視圖控制器彈出右上角的藍色視圖控制器,每種彈出風格的結果在下面顯示。對于某些彈出風格,UIKit在兩個視圖控制器的內容之間插入一個模糊視圖。
圖8-1 全屏彈出風格

提示:使用UIModalPresentationFullScreen風格彈出視圖控制器時,通常UIKit會在過渡動畫完成后移除下面的視圖控制器的視圖。可以通過指定UIModalPresentationOverFullScreen風格來阻止移除這些視圖。當presented視圖控制器有一個透明區域可以讓下面的內容顯示時,可以使用這種風格。
使用其中一種全屏彈出風格時,發起該彈出的視圖控制器本身必須覆蓋整個屏幕。如果presenting視圖控制器沒有覆蓋整個屏幕,UIKit遍歷視圖控制器層級結構,直到找到一個覆蓋整個屏幕的視圖控制器。如果不能找到充滿屏幕的視圖控制器,UIKit使用窗口的根視圖控制器。
1.1.1.2 彈出框(Popover)風格
UIModalPresentationPopover風格在一個彈出框視圖上顯示視圖控制器。彈出框用于顯示額外的信息,或者當前焦點和選中對象相關的項目列表。在水平方向為常規的環境中,彈出框視圖覆蓋屏幕的一部分,如圖8-2所示。在水平方向為緊湊的環境中,彈出框默認適應為UIModalPresentationOverFullScreen彈出風格。在彈出框視圖外點擊會讓它自動消失。
圖8-2 彈出框風格

在水平方向為緊湊的環境中,彈出框會采用全屏彈出,所以通常需要修改彈出框代碼。在全屏模式中,需要一種方式讓彈出框消失。此時可以添加按鈕,嵌入彈出框到一個可消失的容器視圖控制器中,或者改變自適應行為本身。
如何配置彈出框,請參考“Presenting a View Controller in a Popover”。
1.1.1.3 當前上下文(Current Context)風格
UIModalPresentationCurrentContext風格覆蓋界面的一個特定視圖控制器。使用上下文風格時,通過設置視圖控制器的definesPresentationContext屬性為YES來指定覆蓋該視圖控制器。圖8-3中,當前上下文彈出只覆蓋了分割視圖控制器的一個子視圖控制器。
圖8-3 當前上下文彈出風格

提示:使用UIModalPresentationFullScreen風格彈出視圖控制器時,通常UIKit會在過渡動畫完成后移除下面的視圖控制器的視圖。可以通過指定UIModalPresentationOverCurrentContext風格來阻止移除這些視圖。當presented視圖控制器有一個透明區域可以讓下面的內容顯示時,可以使用這種風格。
定義彈出上下文的視圖控制器還可以定義彈出過程中的過渡動畫。通常,UIKit使用presented視圖控制器的modalTransitionStyle屬性值讓視圖控制器產生動畫。如果彈出上下文視圖控制器設置自己的providesPresentationContextTransitionStyle屬性為YES,UIKit使用該視圖控制器的modalTransitionStyle屬性值代替。
過渡到水平方向為緊湊環境時,當前上下文風格適應為UIModalPresentationFullScreen風格。通過使用自適應彈出代理指定不同的彈出風格或視圖控制器來改變這種行為。
1.1.1.4 自定義彈出風格
UIModalPresentationCustom風格可以使用自定義風格彈出視圖控制器。創建自定義風格涉及繼承UIPresentationController,使用它的方法動畫的移動自定義視圖到屏幕上,并設置presented視圖控制器的尺寸和位置。彈出控制器還需要處理presented視圖控制器的特征變化時引起的所有適配工作。
如何定義自定義彈出視圖控制器的信息,請參考“創建自定義彈出”。
1.1.2 過渡(Transition)風格
過渡風格決定了顯示presented視圖控制器的動畫類型。對于內置的過渡風格,可以為想要彈出的視圖控制器的modalTransitionStyle屬性指定一個標準過渡風格。彈出視圖控制器時,UIKit創建相應的動畫風格。例如,圖8-4展示了標準的上滑式(slide-up)過渡(UIModalTransitionStyleCoverVertical)如何動畫的移動視圖口控制器到屏幕上。視圖控制器B從屏幕外開始,動畫向上覆蓋頂層的視圖控制器A。視圖控制器B消失時,動畫反轉,B下滑顯示A。
圖8-4 視圖控制器的過渡動畫

使用動畫對象和過渡代理創建自定義過渡。動畫對象為在屏幕上放置視圖控制器創建過渡動畫。過渡代理在適當的時候為UIKit提供動畫對象。如何實現自定義過渡,請參考“自定義過渡動畫”。
1.1.3 彈出VS顯示視圖控制器
UIViewController類提供兩種方式顯示視圖控制器:
- showViewController:sender:和showDetailViewController:sender:方法是顯示視圖控制器最合適和靈活的方式。這些方法讓presenting視圖控制器決定最好的彈出方式。例如,容器視圖控制器可能把視圖控制器作為子視圖控制器,而不是模態的彈出。默認以模態方式彈出視圖控制器。
- presentViewController:animated:completion:方法總是模態顯示視圖控制器。調用該方法的視圖控制器可能根本不處理彈出,但總是以模態彈出。在水平方向為緊湊的環境中,該方法適用于彈出風格。
showViewController:sender:和showDetailViewController:sender:方法是發起彈出的首選方式。視圖控制器可以在完全不知道視圖控制器層級結構中其它視圖控制器,或者當前視圖控制器在該層級結構中位置的情況下調用這些方法。這些方法可以更容易重用視圖控制器,而不需要編碼額外的條件代碼路徑。
1.2 彈出視圖控制器
有幾種方式可以彈出視圖控制器:
- 使用segue自動彈出視圖控制器。Segue使用界面生成器中指定的信息實例化并彈出視圖控制器。更多如何配置segue的信息,請參考“使用Segues”。
- 調用showViewController:sender:或showDetailViewController:sender:方法顯示視圖控制器。在自定義視圖控制器中,可以改變這些方法的行為,讓她們更適合你的視圖控制器。
- 調用presentViewController:animated:completion:方法模態的彈出視圖控制器。
關于如何關閉視圖控制器,請參考“關閉Presented視圖控制器”。
1.2.1 顯示視圖控制器
使用showViewController:sender:或showDetailViewController:sender:方法時,在屏幕上顯示一個新視圖控制器的過程很簡單:
- 創建要彈出的視圖控制器對象。創建時,用它需要的數據初始化它。
- 設置新視圖控制器的modalPresentationStyle屬性為首選的彈出風格。該風格可能不是最終彈出的風格。
- 設置視圖控制器的modalTransitionStyle屬性為期望的過渡動畫風格。該風格可能不是最終的動畫風格。
- 調用當前視圖控制器的showViewController:sender:和showDetailViewController:sender:方法。
UIKit轉發showViewController:sender:和showDetailViewController:sender:方法的調用給合適的presenting視圖控制器。該視圖控制器決定如何最好的執行彈出,并根據需要修改彈出和過渡風格。例如,導航控制器可能把視圖控制器壓入它的導航棧。
關于顯示視圖控制器和以模態的方式彈出視圖控制器之間的差異,請參考“彈出VS顯示視圖控制器”。
1.2.2 模態的彈出視圖控制器
直接彈出一個視圖控制器時,需要告訴UIKit你想如何顯示新視圖控制器,以及如何動畫的出現在屏幕上。
- 創建想要彈出的視圖控制器。
- 設置新視圖控制器的modalPresentationStyle屬性為首選的彈出風格。
- 設置視圖控制器的modalTransitionStyle屬性為期望的過渡動畫風格。
- 調用當前視圖控制器的presentViewController:animated:completion:方法。
調用presentViewController:animated:completion:方法的視圖控制器可能不是實際執行模態彈出的那個視圖控制器。彈出風格決定視圖控制器如何被彈出,包括presenting視圖控制器需要的特征。例如,全屏彈出要求由全屏視圖控制器發起。如果當前的presenting視圖控制器不合適,UIKit遍歷視圖控制器層級結構,直到找到一個合適的。完成模態彈出后,UIKit更新受影響的視圖控制器的presentingViewController和presentedViewController屬性。
列表8-1描述了如何通過代碼彈出視圖控制器。用戶添加新食譜時,應用程序彈出一個導航控制器提示用戶輸入食譜的基本信息。導航控制器有標準地方放置取消和完成按鈕。使用導航控制器也方便將來擴展新是食譜界面。所要做的所有工作就是把新視圖控制器壓入導航棧。
列表8-1 通過代碼彈出視圖控制器
- (void)add:(id)sender {
// Create the root view controller for the navigation controller
// The new view controller configures a Cancel and Done button for the
// navigation bar.
RecipeAddViewController *addController = [[RecipeAddViewController alloc] init];
addController.modalPresentationStyle = UIModalPresentationFullScreen;
addController.transitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:addController animated:YES completion: nil];
}
1.2.3 在彈出框中彈出視圖控制器
彈出框顯示之前需要額外的配置。設置模態彈出類型為UIModalPresentationPopover后,配置以下彈出相關的屬性:
- 設置視圖控制器的preferredContentSize屬性為期望的尺寸。
- 使用關聯的UIPopoverPresentationController對象設置彈出錨點,該對象通過視圖控制器的popoverPresentationController屬性訪問。設置以下的其中一個:
- 設置barButtonItem屬性為一個導航按鈕項。
- 設置sourceView和sourceRect為其中一個視圖的指定區域。
根據需要,可以使用UIPopoverPresentationController對象調整更多彈出框的外觀。Popover presentation控制器也支持代理對象,可以用來響應彈出過程中的變化。例如,使用代理響應彈出框出現,消失或者重定位。
1.3 關閉Presented視圖控制器
調用presenting視圖控制器的dismissViewControllerAnimated:completion:方法讓presented視圖控制器消失。也可以在presented視圖控制器本身調用該方法。如果在presented視圖控制器中調用該方法,UIKit自動轉發該請求給presenting視圖控制器。
關閉視圖控制器之前,保存該視圖控制器的所有重要信息。關閉視圖控制器會把它從視圖控制器層級結構移除,并從屏幕上移除它的視圖。如果沒有存儲指向該視圖控制器的強引用,關閉會釋放關聯的內容。
使用代理設計模式從presented視圖控制器返回數據給presenting視圖控制器。代理可以更容易的重用視圖控制器。使用代理時,presented視圖控制器存儲一個指向代理對象的引用,該對象實現正式協議的方法。Presented視圖控制器調用代理的這些方法收集結果。典型的做法是,presenting視圖控制器本身作為presented視圖控制器的代理。
1.4 顯示另一個故事版中定義的視圖控制器
可以在同一個故事版的視圖控制器之間創建segue,但不能在不同故事版之間創建segue。想要顯示另一個故事版中的視圖控制器,必須在彈出前顯式的實例化該視圖控制器,如列表8-2所示。該例子模態彈出視圖控制器,你也可以把它壓入導航控制器,或者其它方式顯示。
列表8-2 從故事版中加載視圖控制器
UIStoryboard* sb = [UIStoryboard storyboardWithName:@"SecondStoryboard" bundle:nil];
MyViewController* myVC = [sb instantiateViewControllerWithIdentifier:@"MyViewController"];
// Configure the view controller.
// Display the view controller
[self presentViewController:myVC animated:YES completion:nil];
沒有要求必須創建多個故事版。不過有些情況下,多個故事版很有用:
- 有一個很大的開發團隊,不同的用戶界面由不同人員開發。每個團隊在自己的故事版中開發界面可以最小化沖突。
- 購買或創建的庫中預定義了視圖控制器類型集合;這些視圖控制器的內容由庫中的故事版中提供。
- 有內容需要在外接屏幕上顯示。這種情況下,可以保持所有與另一個屏幕相關的視圖控制器在單獨的故事版中。這種場景下的另一種方式是編寫自定義segue。
2 使用Segues
使用segue定義應用程序界面流。一個segue定義了故事版中兩個視圖控制器之間的過渡。Segue的起點可以是發起segue的按鈕,表格行或者手勢識別器。終點是想要顯示的視圖控制器。Segue總是彈出新視圖控制器,也可以使用unwind segue關閉一個視圖控制器。
圖9-1 兩個視圖控制器之間的segue

不需要通過代碼觸發segue。運行時,UIKit記載關聯視圖控制器的segue,并連接它們到相應的元素。用戶與元素交互時,UIKit加載適當的視圖控制器,通知應用程序segue即將發生,并執行過渡。可以使用UIKit發送的通知,將數據傳遞給新視圖控制器,或者干脆阻止segue的發生。
2.1 在視圖控制器之間創建Segue
通過Control-click第一個視圖控制器中的適當元素,并拖拽到目標視圖控制器,可以在同一個故事版文件的視圖控制器之間創建segue。Segue的起點必須是視圖或者定義了action的對象,例如控件,導航欄按鈕項(bar button item)或者手勢識別器。也可以從基于單元格的視圖,例如表格和集合視圖,創建segue。圖9-2顯示了如何創建segue,點擊表格行時,顯示新視圖控制器。
圖9-2 創建segue關系

提示:有些元素支持多個segues。例如,表格行可以為點擊行的輔助按鈕和其它部分配置不同的segue。
釋放鼠標按鈕后,界面生成器提示你選擇兩個視圖控制器之間的關系類型,如圖9-3所示。選擇與過渡對應的segue。
圖9-3 選擇創建的segue類型

選擇segue的關系類型時,盡可能選擇自適應segue。自適應segue根據當前環境自動調整它們的行為。比如,一個Show segue根據presenting視圖控制器改變行為。提供非自適應segue的應用程序必須也能在iOS 7上運行,因為它不支持自適應segue。表格9-1列出了自適應segues,以及它們在應用程序中的行為。
表格9-1 自適應segue類型
Segue類型 | 行為 |
---|---|
Show (Push) | 該segue使用目標視圖控制器的showViewController:sender:方法顯示新內容。對于大部分視圖控制器,該segue在源視圖控制器上模態顯示新內容。有些視圖控制器覆寫該方法實現不同的行為。例如,導航控制器把新視圖控制器壓入導航棧。 UIkit使用targetViewControllerForAction:sender:方法定位源視圖控制器。 |
Show Detail (Replace) | 該segue使用目標視圖控制器的showDetailViewController:sender:方法顯示新內容。該segue只與嵌入UISplitViewController對象的視圖控制器相關。使用該segue,分割視圖控制器用新內容代替第二個子視圖控制器(詳細視圖控制器)。其它大部分視圖控制器模態的顯示新內容。 UIKit使用targetViewControllerForAction:sender:方法定位源視圖控制器。 |
Present Modally | 該segue使用指定的彈出和過渡風格顯示視圖控制器。定義了適當彈出上下文的視圖控制器處理實際的彈出。 |
Present as Popover | 在水平方向為常規的環境中,視圖控制器以彈出框的形式出現。在水平方向為緊湊的環境中,視圖控制器使用全屏模態方式彈出視圖控制器。 |
創建segue后,選中segue對象,使用屬性(attributes)檢查器指定一個標識符。在segue期間,使用該標識符確定觸發了哪個segue,尤其在視圖控制器支持多個segues時很有用。執行segue時,包含該標識符的UIStoryboardSegue對象會傳遞給視圖控制器。
2.2 運行時修改Segue的行為
圖9-4顯示了觸發segue時發生了什么。大部分工作發生在presenting視圖控制器中,它管理過渡到新視圖控制器。新視圖控制器的配置過程基本與自己創建視圖控制器并彈出它相同。因為segue在故事版中配置,所以segue涉及的兩個視圖控制器必須在同一個故事版中。
圖9-4 使用segue顯示視圖控制器

在segue期間,UIKit調用當前視圖控制器的方法,讓你有機會控制segue的結果。
- 在shouldPerformSegueWithIdentifier:sender:方法可以阻止segue發生。該方法返回NO會導致segue悄悄地失敗,但不會阻止其它動作發生。例如,點擊表格行仍然會導致表格調用相關的代理方法。
- 源視圖控制器的prepareForSegue:sender:可以傳遞數據到目標視圖控制器。傳遞到該方法的UIStoryboardSegue對象包含一個目標視圖控制器的引用和其它segue相關的信息。
2.3 創建Unwind Segue
Unwind segue可以關閉彈出的視圖控制器。通過在界面生成器中鏈接按鈕或其它合適對象到當前視圖控制器的Exit對象來創建unwind segue。用戶點擊按鈕或與其它合適對象交互時,UIKit搜索在視圖控制器層級結構中搜索可以處理unwind segue的對象。然后關閉當前視圖控制器和unwind segue目標之間的所有視圖控制器。
創建unwind segue的步驟如下:
- 選擇unwind segue結束后顯示在屏幕上的視圖控制器。
- 在選擇的視圖控制器中定義unwind action方法。
該方法的Swift語法如下:
@IBAction func myUnwindAction(unwindSegue: UIStoryboardSegue)
該方法的Objective-C語法如下:
- (IBAction)myUnwindAction:(UIStoryboardSegue*)unwindSegue
- 導航到發起unwind action的視圖控制器。
- Control-click發起unwind segue的按鈕(或其它對象)。該元素在想要關閉的視圖控制器中。
-
拖拽到視圖控制器場景頂部的Exit對象。
- 從關系面板上選擇unwind action方法。
在界面生成器中創建相應的unwind segue之前,必須在其中一個視圖控制器中定義unwind action方法。該方法是必須的,它告訴界面生成器unwind segue有一個有效的目標。
在unwind action方法中執行應用程序的具體任務。不用自己關閉涉及segue的任何視圖控制器;UIKit替你完成了這項工作。相反,使用segue對象獲得被關閉的視圖控制器,并從中接收數據。也可以在unwind segue完成之前,在unwind action中更新當前視圖控制器。
2.4 通過代碼發起Segue
通常,segue的觸發是因為在故事版文件中創建的連接。然而,有些時候不能在故事版中創建segue,可能因為目標視圖控制器還沒有確定。例如,一個游戲應用程序可能根據游戲的結果過渡到不同界面。這些情況下,可以在當前視圖控制器中調用performSegueWithIdentifier:sender:方法,通過代碼觸發segue。
列表9-1中,當從豎屏旋轉到橫屏時,segue彈出一個特殊的視圖控制器。因為通知對象沒有為執行segue命令提供有用的信息,所以視圖控制器指定自己為segue的sender。
列表9-1 通過代碼觸發segue
- (void)orientationChanged:(NSNotification *)notification {
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) &&
!isShowingLandscapeView) {
[self performSegueWithIdentifier:@"DisplayAlternateView" sender:self];
isShowingLandscapeView = YES;
}
// Remainder of example omitted.
}
2.5 創建自定義Segue
界面生成器為所有從一個視圖控制器過渡到另一個(從presenting視圖控制器到在彈出框中顯示視圖控制器)的標準方式提供segue。如果這些都不是你想要的,可以創建自定義segue。
2.5.1 Segue的生命周期
要理解自定義segue如何工作,需要理解segue對象的生命周期。Segue對象是UIStoryboardSegue類或它的子類的實例。應用程序永遠不是直接創建segue對象;Segue觸發時,UIKit自動創建它們。以下是創建segue的過程:
- 創建和初始化被彈出的視圖控制器。
- 創建segue對象,并調用它的initWithIdentifier:source:destination:方法。Segue的標識符是在界面生成器中提供的唯一字符串,另外兩個參數代表過渡中的兩個控制器對象。
- 調用presenting視圖控制器的prepareForSegue:sender:方法。參考“運行時修改Segue的行為”。
- 調用segue對象的perform方法。該方法執行過渡,把新視圖控制器顯示在屏幕上。
- 釋放segue對象的引用。
2.5.2 實現自定義Segue
通過繼承UIStoryboardSegue并實現以下方法來實現自定義segue:
- 覆寫initWithIdentifier:source:destination:方法,并用它初始化自定義segue對象。總要先調用super方法。
- 實現perform方法,并用它配置過渡動畫。
提示:如果實現中添加了屬性來配置segue,不能再界面生成器中配置這些屬性。相反,在觸發segue的源視圖控制器的prepareForSegue:sender:方法中配置自定義的segue的額外屬性。
列表9-2是一個很簡單的自定義segue。這個例子不用任何形式的動畫彈出目標視圖控制器,但可以擴展實現自己的動畫。
列表9-2 自定義segue
- (void)perform {
// Add your own animation code here.
[[self sourceViewController] presentViewController:[self destinationViewController] animated:NO completion:nil];
}
3 自定義過渡動畫
過渡動畫為應用程序界面變化提供視覺反饋。UIKit提供了一組標準過渡類型用于彈出視圖控制器,可以通過自定義過渡補充標準過渡。
3.1 過渡動畫序列
過渡動畫交換兩個視圖控制器的內容。有兩種類型的過渡:彈出和關閉(dismissal)。彈出過渡添加一個新視圖控制器到視圖控制器層級結構,而關閉過渡從層級結構中移除一個或多個視圖控制器。
實現過渡動畫涉及很多對象。UIKit提供了所有涉及過渡的對象的默認版本,可以自定義所有或其中一部分對象。如果選擇了一組正確的對象,只需要少量代碼就能創建動畫。如果利用UIKit提供的現有代碼,甚至可以很容易創建包括交互的動畫。
3.1.1 過渡代理
過渡代理是過渡動畫和自定義彈出的起點。過渡代理是遵循UIViewControllerTransitioningDelegate協議的對象。它的任務是為UIKit提供以下對象:
- 動畫對象。動畫對象負責創建動畫,用于顯示或隱藏視圖控制器的視圖。過渡代理可以為顯示和關閉視圖控制器提供獨立的動畫對象。動畫對象遵循UIViewControllerAnimatedTransitioning協議。
-
交互式動畫對象。交互式動畫對象使用觸摸事件或手勢識別器驅動自定義動畫的時間。交互式動畫對象遵循UIViewControllerInteractiveTransitioning協議。
創建交互式動畫對象最簡單的方式是繼承UIPercentDrivenInteractiveTransition類,并在子類中添加事件處理代碼。該類使用現有的動畫對象控制動畫創建時間。如果創建自己的可交互動畫對象,必須自己渲染動畫的每一幀。 - 彈出(presentation)控制器。彈出控制器管理視圖控制器出現在屏幕上的彈出風格。系統為內置的彈出風格提供了彈出控制器,也可以為自己的彈出風格提供自定義彈出控制器。更多關于創建自定義彈出控制器的信息,請參考“創建自定義彈出”。
給視圖控制器的transitioningDelegate屬性指定一個過渡代理,告訴UIKit你想執行一個自定義過渡或彈出。可以選擇代理提供哪些對象。如果不提供動畫對象,UIKit使用視圖控制器的modalTransitionStyle屬性中的標準過渡動畫。
圖10-1顯示了presented試圖控制器中過渡代理和動畫對象之間的關系。只有當視圖控制器的modalPresentationStyle屬性設置為UIModalPresentationCustom時,才使用彈出控制器。
圖10-1 自定義彈出和動畫對象

關于如何實現過渡代理,請參考“實現過渡代理”。關于過渡代理的方法,請參考“UIViewControllerTransitioningDelegate Protocol Reference“。
3.1.2 自定義動畫序列
當presented視圖控制器的transitioningDelegate包含一個有效的對象時,UIKit使用你提供的自定義動畫對象彈出視圖控制器。準備彈出時,UIKit調用過渡代理的animationControllerForPresentedController:presentingController:sourceController:方法接收自定義動畫對象。如果對象可用,UIKit執行以下步驟:
- UIKit調用過渡代理的interactionControllerForPresentation:方法,查看交互式動畫對象是否可用。如果該方法返回nil,UIKit執行沒有用戶交互的動畫。
- UIKit調用動畫對象的transitionDuration:方法獲得動畫持續時間。
- UIKit調用合適的方法啟動動畫:
- 對于非交互式動畫,UIKit調用動畫對象的animateTransition:方法。
- 對于交互式動畫,UIKit調用交互式動畫對象的startInteractiveTransition:方法。
- UIKit等待動畫對象調用上下文過渡對象的completeTransition:方法。
動畫完成后,自定義動畫對象調用該方法,典型的做法是在動畫完成塊中。調用該方法結束過渡,讓UIKit知道,它可以調用完成處理器的presentViewController:animated:completion:方法和動畫對象本身的animationEnded:方法。
關閉(dismiss)視圖控制器時,UIKit調用過渡代理的animationControllerForDismissedController:方法,并執行以下步驟:
- UIKit調用過渡代理的interactionControllerForDismissal:方法,查看可交互動畫對象是否可用。如果該方法返回nil,UIKit執行沒有用戶交互的動畫。
- UIKit調用動畫對象的transitionDuration:方法獲得動畫持續時間。
- UIKit調用合適的方法啟動動畫:
- 對于非交互式動畫,UIKit調用動畫對象的animateTransition:方法。
- 對于交互式動畫,UIKit調用交互式動畫對象的startInteractiveTransition:方法。
- UIKit等待動畫對象調用上下文過渡對象的completeTransition:方法。
動畫完成后,自定義動畫對象調用該方法,典型的做法是在動畫完成塊中。調用該方法結束過渡,讓UIKit知道,它可以調用完成處理器的presentViewController:animated:completion:方法和動畫對象本身的animationEnded:方法。
重要:必須在動畫結束時調用completeTransition:方法。UIKit不會結束過渡過程,所以直到調用該方法,才會返回把控制返回應用程序。
3.1.3 過渡上下文對象
過渡動畫開始之前,UIKit創建過渡上下文對象,并用如何執行動畫的信息填充。過渡上下文對象是代碼中的重要部分。它實現了UIViewControllerContextTransitioning協議,并儲存過渡中涉及的視圖控制器和視圖的引用。還存儲應該如何執行過渡的信息,包括動畫是否可交互。動畫對象需要這些所有信息來設置和執行實際的動畫。
重要:設置自定義動畫時,總是使用過渡上下文對象中的對象和屬性,而不是自己管理的緩存信息。過渡發生在各種條件下,有些可能會改變動畫參數。過渡上下文對象保證有執行動畫的正確信息,緩存的信息可能在掉用動畫對象方法時已經過期。
圖10-2展示了過渡上下文對象如何與其它對象交互。動畫對象在animateTransition:方法中接收該對象。創建的動畫發生在提供的容器視圖中。例如,彈出視圖控制器時,添加它的視圖作為容器視圖的子視圖。容器視圖可能是窗口或者一個普通視圖,但總是配置來運行動畫。
圖10-2 過渡上下文對象

關于過渡上下文對象的更多信息,請參考”UIViewControllerContextTransitioning Protocol Reference“。
3.1.4 過渡協調器(Coordinator)
對于內置過渡和自定義過渡,UIKit創建一個過渡協調器對象幫助執行需要的額外動畫。除了視圖控制器的彈出和關閉,過渡還發生在界面旋轉或視圖控制器的frame變化時。所有這些過渡都代表視圖層級結構的變化。過渡協調器是同時追蹤這些變化和讓內容產生動畫的一種方式。通過受影響的視圖控制器的transitionCoordinator屬性訪問過渡協調器。過渡協調器只存在過渡期間。
圖10-3展示了過渡協調器和涉及彈出的視圖控制器之間的關系。使用過渡協調器可以獲得過渡信息,以及注冊想要同時執行的過渡動畫的動畫塊。過渡協調器對象遵循UIViewControllerTransitionCoordinatorContext協議,該協議提供時間信息,動畫當前的狀態信息,以及過渡中涉及的視圖和視圖控制器。執行動畫塊時,它們接收同樣信息的上下文對象。
圖10-3 過渡協調器

關于過渡協調器對象的更多信息,請參考”UIViewControllerTransitionCoordinator Protocol Reference“。使用配置動畫的上下文信息,請參考”UIViewControllerTransitionCoordinatorContext Protocol Reference“。
3.2 使用自定義動畫彈出視圖控制器
在現有視圖控制器的動作方法中完成以下工作,來使用自定義動畫彈出視圖控制器:
- 創建想要彈出的視圖控制器。
- 創建自定義的過渡代理對象,并分配給視圖控制器的transitioningDelegate屬性。請求時,過渡代理的方法應該創建并返回自定義動畫對象。
- 調用presentViewController:animated:completion:方法彈出視圖控制器。
調用presentViewController:animated:completion:方法時,UIKit啟動彈出過渡。彈出從下一個運行循環迭代開始,一直持續到自定義動畫對象調用completeTransition:方法。可交互過渡允許過渡過程中處理觸摸事件,而非交互式過渡在動畫對象指定的周期內運行。
3.3 實現過渡代理
過渡代理的目的是創建并返回自定義對象。列表10-1展示了過渡方法的可以很簡單的實現。例子中創建并返回自定義動畫對象。大部分實際工作由動畫對象本身處理。
列表10-1 創建動畫對象
- (id<UIViewControllerAnimatedTransitioning>)
animationControllerForPresentedController:(UIViewController *)presented
presentingController:(UIViewController *)presenting
sourceController:(UIViewController *)source {
MyAnimator* animator = [[MyAnimator alloc] init];
return animator;
}
過渡代理的其它方法跟上面例子中一樣簡單。可以根據應用程序的當前狀態合并自定義邏輯,返回不同的動畫對象。關于過渡代理方法的更多信息,請參考“UIViewControllerTransitioningDelegate Protocol Reference”。
3.4 實現動畫對象
動畫對象可以是遵循UIViewControllerAnimatedTransitioning協議的任何對象。動畫對象創建在固定時間內執行的動畫。動畫對象的關鍵是animateTransition:方法,使用該方法創建實際的動畫。動畫過程大致分為以下幾個部分:
- 獲取動畫參數。
- 使用核心動畫(Core Animation)或UIView動畫方法創建動畫。
- 清理和完成過渡。
3.4.1 獲取動畫參數
傳遞給animateTransition:方法的上下文過渡對象包含執行動畫需要的數據。可以從上下文過渡對象中獲得最新信息時,永遠不要使用緩存信息或者從視圖控制器獲取信息。彈出和關閉視圖控制器有時會涉及視圖控制器之外的對象。例如,自定義彈出控制器(presentation controller)可能會添加背景視圖作為彈出的一部分。上下文過渡對象會把額外的視圖和對象考慮在內,并為動畫提供正確的視圖。
- 調用viewControllerForKey:方法兩次,獲得過渡中的”from“和”to“視圖控制器。永遠不要認為你知道哪些視圖控制器參與過渡。適應新特征環境或者響應應用程序請求時,UIKit可能會改變視圖控制器。
- 調用containerView方法獲得動畫的父視圖。添加所有關鍵子視圖到該視圖。例如,彈出過程中,添加presented視圖控制器的視圖到該視圖。
- 調用viewForKey:方法獲得添加或刪除的視圖。視圖控制器的視圖可能不是唯一一個在過渡期間添加或刪除的視圖。彈出控制器可能會在層級結構中插入視圖,這些視圖也必須添加或刪除。viewForKey:方法返回根視圖,該視圖包含所有需要添加或刪除的所有東西。
- 調用finalFrameForViewController:方法獲得被添加或刪除視圖的最終frame矩形。
上下文過渡對象使用”from“和”to“術語識別過渡中涉及的視圖控制器,視圖和frame矩形。”from“視圖控制器總是在過渡開始時顯示在屏幕,”to“視圖控制器在過渡結束后顯示在屏幕上。如圖10-4,”from“和”to”視圖控制器在彈出和關閉之間交換位置。
圖10-4 “from”和“to”對象

交換值可以編寫一個動畫對象同時處理彈出和關閉。設計動畫對象時,只需要包含一個屬性,用來識別是彈出動畫還是關閉動畫。兩者的唯一區別如下:
- 對于彈出,添加“to”視圖到容器視圖層級結構。
- 對于關閉,從容器視圖層級結構移除“from”視圖。
3.4.2 創建過渡動畫
在典型的彈出中,屬于presented視圖控制器的視圖動畫的移動到位置上。其它視圖可能作為彈出的一部分發生動畫,但動畫的主要目標總是添加到視圖層級結構的視圖。
讓主視圖發生動畫時,配置動畫的基本動作是一樣的。從過渡上下文對象中獲取需要的對象和數據,并用這些信息創建實際的動畫。
- 彈出動畫:
- 使用viewControllerForKey:和viewForKey:方法檢索過渡中涉及的視圖控制器和視圖。
- 設置“to”視圖的起始位置。同時設置其它屬性的初始值。
- 從上下文過渡對象的finalFrameForViewController:方法獲得“to”視圖的結束位置。
- 添加“to”視圖作為容器視圖的子視圖。
- 創建動畫。
- 在動畫塊中,動畫的移動“to”視圖到容器視圖中的結束位置。同時設置其它屬性為最終值。
- 在完成塊中,調用completeTransition:方法,并執行所有其它的清理。
- 關閉動畫:
- 使用viewControllerForKey:和viewForKey:方法檢索過渡中涉及的視圖控制器和視圖。
- 計算“from”視圖的結束位置。該視圖屬于準備關閉的presented視圖控制器。
- 添加“to”視圖作為容器視圖的子視圖。彈出期間,過渡完成后,屬于presenting視圖控制器的視圖會被移除。因此,在關閉操作中,必須添加該視圖到容器中。
- 創建動畫。
- 在動畫塊中,動畫的移動“from”視圖到容器視圖中的結束位置。同時設置其它屬性為最終值。
- 在完成塊中,從視圖層級結構中移除”from“視圖,并調用completeTransition:方法。根據需要執行清理工作。
圖10-5展示了一個自定義彈出和關閉過渡,沿對角線動畫移動視圖。彈出過程中,presented視圖從屏幕外開始,沿對角線向左上角移動直到完全可見。關閉過程中,視圖反轉方向,沿對角線向右下角移動,直到移出屏幕。
圖10-5 自定義彈出和關閉

列表10-2是如何實現圖10-5中過渡的代碼。檢索到動畫需要的對象后,animateTransition:方法計算受影響視圖的frame矩形。彈出時,toView變量代表presented視圖。關閉時,fromView變量代碼dismissed視圖。presenting屬性是動畫對象本身的自定義屬性,過渡代理創建動畫對象時,設置為一個合適的值。
列表10-2 實現沿對角線彈出和關閉動畫
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
// Get the set of relevant objects.
UIView *containerView = [transitionContext containerView];
UIViewController *fromVC = [transitionContext
viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext
viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
// Set up some variables for the animation.
CGRect containerFrame = containerView.frame;
CGRect toViewStartFrame = [transitionContext initialFrameForViewController:toVC];
CGRect toViewFinalFrame = [transitionContext finalFrameForViewController:toVC];
CGRect fromViewFinalFrame = [transitionContext finalFrameForViewController:fromVC];
// Set up the animation parameters.
if (self.presenting) {
// Modify the frame of the presented view so that it starts
// offscreen at the lower-right corner of the container.
toViewStartFrame.origin.x = containerFrame.size.width;
toViewStartFrame.origin.y = containerFrame.size.height;
}
else {
// Modify the frame of the dismissed view so it ends in
// the lower-right corner of the container view.
fromViewFinalFrame = CGRectMake(containerFrame.size.width,
containerFrame.size.height,
toView.frame.size.width,
toView.frame.size.height);
}
// Always add the "to" view to the container.
// And it doesn't hurt to set its start frame.
[containerView addSubview:toView];
toView.frame = toViewStartFrame;
// Animate using the animator's own duration value.
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
if (self.presenting) {
// Move the presented view into position.
[toView setFrame:toViewFinalFrame];
}
else {
// Move the dismissed view offscreen.
[fromView setFrame:fromViewFinalFrame];
}
}
completion:^(BOOL finished){
BOOL success = ![transitionContext transitionWasCancelled];
// After a failed presentation or successful dismissal, remove the view.
if ((self.presenting && !success) || (!self.presenting && success)) {
[toView removeFromSuperview];
}
// Notify UIKit that the transition has finished
[transitionContext completeTransition:success];
}];
}
3.4.3 動畫完成的清理工作
過渡動畫結束后,關鍵要調用completeTransition:方法。調用該方法告訴UIKit過渡完成,用戶可能開始使用presented視圖控制器。調用該方法還會觸發一連串其它完成處理程序,包括presentViewController:animated:completion:方法和動畫對象自身的animationEnded:方法。最好在動畫塊的完成處理程序中調用animationEnded:方法。
因為過渡可能被取消,所以應該使用上下文對象的transitionWasCancelled方法的返回值來決定需要什么清理工作。當取消彈出后時,動畫對象必須撤銷對視圖層級結構做的所有修改。一個成功的關閉也需要類似的動作。
3.5 在過渡中添加交互
讓動畫可交互最簡單的方式是使用UIPercentDrivenInteractiveTransition對象。該對象與現有的動畫對象協作控制動畫的時間。通過提供的完成百分比完成這項工作。只需要設置事件處理代碼,用來計算完成百分比,并在每一個新事件到達時更新百分比。
可以繼承或者不繼承UIPercentDrivenInteractiveTransition類。如果繼承,使用子類的init方法(或者startInteractiveTransition:方法)執行事件處理代碼的一次性設置。然后使用自定義事件處理代碼計算新的完成百分比,并調用updateInteractiveTransition:方法。當代碼確定過渡完成,調用finishInteractiveTransition方法。
列表10-3展示了UIPercentDrivenInteractiveTransition子類的startInteractiveTransition:方法的自定義實現。該方法設置一個拖動手勢識別器(pan-gesture recognizer)來追蹤觸摸事件,并為動畫設置該手勢識別器到容器視圖。同時保存一個過渡上下文的引用以備后用。
列表10-3 配置百分比驅動的可交互動畫
- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
// Always call super first.
[super startInteractiveTransition:transitionContext];
// Save the transition context for future reference.
self.contextData = transitionContext;
// Create a pan gesture recognizer to monitor events.
self.panGesture = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(handleSwipeUpdate:)];
self.panGesture.maximumNumberOfTouches = 1;
// Add the gesture recognizer to the container view.
UIView* container = [transitionContext containerView];
[container addGestureRecognizer:self.panGesture];
}
手勢識別器為每一個到達的心事件調用動作方法。動作方法的實現可以使用手勢識別器的狀態信息來決定手勢是否成功,失敗或者仍在進行中。同時,可以使用最新的觸摸事件信息來計算手勢的新百分比。
列表10-4展示了列表10-3中配置的拖動手勢識別器的動作方法。新事件到達時,該方法使用垂直移動距離來計算動畫的完成百分比。手勢結束時,該方法完成過渡。
列表10-4 使用事件更新動畫過程
-(void)handleSwipeUpdate:(UIGestureRecognizer *)gestureRecognizer {
UIView* container = [self.contextData containerView];
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
// Reset the translation value at the beginning of the gesture.
[self.panGesture setTranslation:CGPointMake(0, 0) inView:container];
}
else if (gestureRecognizer.state == UIGestureRecognizerStateChanged) {
// Get the current translation value.
CGPoint translation = [self.panGesture translationInView:container];
// Compute how far the gesture has travelled vertically,
// relative to the height of the container view.
CGFloat percentage = fabs(translation.y / CGRectGetHeight(container.bounds));
// Use the translation value to update the interactive animator.
[self updateInteractiveTransition:percentage];
}
else if (gestureRecognizer.state >= UIGestureRecognizerStateEnded) {
// Finish the transition and remove the gesture recognizer.
[self finishInteractiveTransition];
[[self.contextData containerView] removeGestureRecognizer:self.panGesture];
}
}
提示:計算的值代表整個動畫長度的完成百分比。對于可交互動畫,可能希望非線性效果,例如動畫本身的初速度,阻尼值和非線性完成曲線。這些效果試圖把事件的觸摸位置從底層視圖的移動中分離。
3.6 創建與過渡并存的動畫
過渡中涉及的視圖控制器可以在彈出或過渡動畫之上執行額外的動畫。例如,presented視圖控制器過渡中,可能在自身的視圖層級結構上執行一些動畫,并在過渡發生時添加運動效果或其它可視化反饋。任何可以訪問presented或者presenting視圖控制器transitionCoordinator屬性的對象都可以執行動畫。過渡協調器只存在過渡過程中。
調用過渡協調器的animateAlongsideTransition:completion:或者animateAlongsideTransitionInView:animation:completion:方法創建動畫。提供的塊一直被存儲,直到過渡動畫開始,此時它們與過渡動畫一起執行。
3.7 在彈出控制器(Presentation Controller)中使用動畫
對于自定義的彈出,可以提供自己的彈出控制器,給presented視圖控制器一個自定義外觀。彈出控制器管理獨立于視圖控制器和它內容的自定義chrome。例如,彈出控制器管理一個放置在視圖控制器的視圖之后的模糊視圖。事實上,彈出控制器不管理特定控制器的視圖,所以可以在任何視圖控制器中使用相同的彈出控制器。
通過presented視圖控制器的過渡代理提供自定義彈出控制器。(視圖控制器的modalTransitionStyle屬性必須為UIModalPresentationCustom。)彈出控制器與其它動畫對象一起運行。動畫對象執行視圖控制器的視圖動畫時,彈出控制器執行額外視圖的動畫。過渡結束時,彈出控制器有機會執行對視圖層級結構進行最后的調整。
關于如何創建自定義彈出控制器的信息,請參考“創建自定義彈出”。
4 創建自定義彈出
UIKit分離視圖控制器的內容與彈出和顯示內容的方式。底層的彈出控制器對象管理presented視圖控制器,彈出控制器管理顯示視圖控制器視圖的視覺風格。彈出控制器可能執行以下操作:
- 設置presented視圖控制器的尺寸。
- 添加自定義視圖來改變presented內容的視覺外觀。
- 為它的自定義視圖提供過渡動畫。
- 應用程序環境變化時,適應彈出的視覺外觀。
UIKit為標準彈出風格提供了彈出控制器。設置視圖控制器的彈出風格為UIModalPresentationCustom,并提供一個合適的過渡代理時,UIKit使用自定義彈出控制器。
4.1 自定義彈出過程
彈出一個UIModalPresentationCustom風格的視圖控制器時,UIKit查找一個自定義彈出控制器來管理彈出過程。彈出過程中,UIKit調用彈出控制器的方法設置自定義視圖,并動畫的移動視圖到指定位置。
彈出控制器與動畫對象一起實現整個過渡。動畫對象動畫的移動視圖控制器的內容到屏幕上,彈出控制器處理其它事情。通常情況下,彈出控制器動畫移動自己的視圖,但也可以覆寫彈出控制器的presentedView方法,讓動畫對象執行所有或部分視圖的動畫。
彈出過程中,UIKit:
- 調用過渡代理的presentationControllerForPresentedViewController:presentingViewController:sourceViewController:檢索自定義彈出控制器。
- 向過渡代理請求動畫對象和可交互動畫對象,如果有的話。
- 調用彈出控制器的presentationTransitionWillBegin方法。
該方法的實現應該添加任意自定義視圖到視圖層級結構,并配置這些視圖的動畫。 - 從彈出控制器中獲得presentedView。
該方法返回的視圖由動畫對象動畫的移動到指定位置。通常情況下,該方法返回presented視圖控制器的根視圖。如果需要,彈出控制器可以使用自定義背景視圖代替該視圖。如果指定了另一個視圖,必須把presented視圖控制器的根視圖嵌入到視圖層級結構中。 - 執行過渡動畫。
過渡動畫包括動畫對象創建的主動畫,以及配置與主動畫一起運行的動畫。關于過渡動畫的信息,請參考“過渡動畫序列”。
在動畫過程中,UIKit調用彈出控制器的containerViewWillLayoutSubviews和containerViewDidLayoutSubviews方法,這樣你可以根據需要調整自定義視圖的布局。 - 過渡動畫完成后,調用presentationTransitionDidEnd:方法。
關閉過程中,UIKit:
- 從當前可見視圖控制器中獲得自定義彈出控制器。
- 向過渡代理請求動畫對象和可交互動畫對象,如果有的話。
- 調用彈出控制器的dismissalTransitionWillBegin方法。
該方法的實現應該添加任意自定義視圖到視圖層級結構,并配置這些視圖的動畫。 - 從彈出控制器獲得presentedView。
- 執行過渡動畫。
過渡動畫包括動畫對象創建的主動畫,以及配置與主動畫一起運行的動畫。關于過渡動畫的信息,請參考“過渡動畫序列”。
在動畫過程中,UIKit調用彈出控制器的containerViewWillLayoutSubviews和containerViewDidLayoutSubviews方法,這樣你可以移除自定義約束。 - 過渡動畫完成后,調用dismissalTransitionDidEnd:方法。
在彈出過程中,彈出控制器的frameOfPresentedViewInContainerView和presentedView方法可能調用多次,因此這些方法的實現應該盡快返回。同時,presentedView方法的實現不應該試圖設置視圖層級結構。該方法調用時,視圖層級結構以及配置好了。
4.2 創建自定義彈出控制器
要實現自定義彈出風格,需要繼承UIPresentationController類,并添加創建視圖和動畫的代碼。創建自定義彈出控制器時,考慮以下問題:
- 想要添加什么視圖?
- 希望如何動畫的移動額外的視圖到屏幕上?
- presented視圖控制器的尺寸是多少?
- 彈出如何適應水平方向常規和水平方向緊湊的size class?
- 彈出完成時,presenting視圖控制器的視圖是否應該移除?
所有這些決定需要覆寫UIPresentationController類的不同方法。
4.2.1 設置Presented視圖控制器的Frame
可以修改presented視圖控制器的frame矩形,這樣它可以值填充可用空間的一部分。默認情況下,presented視圖控制器完全填充容器視圖的frame。覆寫彈出控制器的frameOfPresentedViewInContainerView方法修改frame矩形。列表11-1中,frame修改為只覆蓋容器視圖的右半邊。這種情況下,彈出控制器使用背景模糊的視圖覆蓋容器的另一半。
列表11-1 改變presented視圖控制器的frame
- (CGRect)frameOfPresentedViewInContainerView {
CGRect presentedViewFrame = CGRectZero;
CGRect containerBounds = [[self containerView] bounds];
presentedViewFrame.size = CGSizeMake(floorf(containerBounds.size.width / 2.0),
containerBounds.size.height);
presentedViewFrame.origin.x = containerBounds.size.width -
presentedViewFrame.size.width;
return presentedViewFrame;
}
4.2.2 管理和動畫自定義視圖
自定義彈出通常涉及添加自定義視圖到presented內容。使用自定義視圖實現純視覺裝飾,或者使用它們為彈出添加實際行為。例如,背景視圖可能包括手勢識別器,用來追蹤發生在presented內容的bounds之外的特定動作。
彈出控制器負責創建和管理彈出中相關的所有自定義視圖。通常在彈出控制器的初始化中創建自定義視圖。列表11-2是一個自定義視圖控制器的初始化方法,其中創建了自己的模糊視圖。該方法創建視圖并執行最低限度的配置。
列表11-2 初始化彈出控制器
- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController
presentingViewController:(UIViewController *)presentingViewController {
self = [super initWithPresentedViewController:presentedViewController
presentingViewController:presentingViewController];
if(self) {
// Create the dimming view and set its initial appearance.
self.dimmingView = [[UIView alloc] init];
[self.dimmingView setBackgroundColor:[UIColor colorWithWhite:0.0 alpha:0.4]];
[self.dimmingView setAlpha:0.0];
}
return self;
}
使用presentationTransitionWillBegin方法動畫的移動自定義視圖到屏幕上。在該方法中,配置自定義視圖,并把它們添加到容器視圖,如列表11-3所示。使用presented或presenting視圖控制器的過渡協調器創建任意動畫。不要在該方法中修改presented視圖控制器的視圖。動畫對象負責動畫移動presented視圖控制器到frameOfPresentedViewInContainerView方法返回的frame矩形中。
列表11-3 動畫移動模糊視圖到屏幕上
- (void)presentationTransitionWillBegin {
// Get critical information about the presentation.
UIView* containerView = [self containerView];
UIViewController* presentedViewController = [self presentedViewController];
// Set the dimming view to the size of the container's
// bounds, and make it transparent initially.
[[self dimmingView] setFrame:[containerView bounds]];
[[self dimmingView] setAlpha:0.0];
// Insert the dimming view below everything else.
[containerView insertSubview:[self dimmingView] atIndex:0];
// Set up the animations for fading in the dimming view.
if([presentedViewController transitionCoordinator]) {
[[presentedViewController transitionCoordinator]
animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>
context) {
// Fade in the dimming view.
[[self dimmingView] setAlpha:1.0];
} completion:nil];
}
else {
[[self dimmingView] setAlpha:1.0];
}
}
彈出結束后,使用presentationTransitionDidEnd:方法完成取消彈出導致的清理工作。如果閥值不滿足條件,可交互動畫對象可能取消過渡。發生這種情況時,UIKit用NO值調用presentationTransitionDidEnd:方法。取消發生時,移除彈出開始時添加的所有自定義視圖,并返回其它視圖到上一個配置,如列表11-4所示。
列表11-4 處理取消的彈出
- (void)presentationTransitionDidEnd:(BOOL)completed {
// If the presentation was canceled, remove the dimming view.
if (!completed)
[self.dimmingView removeFromSuperview];
}
視圖控制器關閉時,使用dismissalTransitionDidEnd:方法從視圖層級結構中移除自定義視圖。如果想要視圖動畫的消失,在dismissalTransitionWillBegin方法設置這些動畫。(原文是在dismissalTransitionDidEnd:方法中設置動畫,應該是官方文檔筆誤。如果我說錯了,請留言指出。)列表11-5是兩個方法的實現,其中移除了上一個例子中的模糊視圖。總是需要檢查dismissalTransitionDidEnd:方法的參數,查看關閉是成功了還是取消了。
列表11-5 關閉彈出的視圖
- (void)dismissalTransitionWillBegin {
// Fade the dimming view back out.
if([[self presentedViewController] transitionCoordinator]) {
[[[self presentedViewController] transitionCoordinator]
animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>
context) {
[[self dimmingView] setAlpha:0.0];
} completion:nil];
}
else {
[[self dimmingView] setAlpha:0.0];
}
}
- (void)dismissalTransitionDidEnd:(BOOL)completed {
// If the dismissal was successful, remove the dimming view.
if (completed)
[self.dimmingView removeFromSuperview];
}
4.3 暴露(Vending)彈出控制器給UIKit
彈出視圖控制器時,使用自定義彈出控制器執行以下步驟顯示視圖控制器:
- 設置presented視圖控制器的modalPresentationStyle屬性為UIModalPresentationCustom。
- 為presented視圖控制器的transitioningDelegate屬性指定一個過渡代理。
- 實現過渡代理的presentationControllerForPresentedViewController:presentingViewController:sourceViewController:方法。
UIKit需要彈出控制器時,會調用過渡代理的presentationControllerForPresentedViewController:presentingViewController:sourceViewController:方法。該方法的實現應該跟列表11-6這么簡單。簡單地創建彈出控制器,然后配置并返回。如果該方法返回nil,UIKit使用全屏彈出風格彈出視圖控制器。
列表11-6 創建自定義彈出控制器
- (UIPresentationController *)presentationControllerForPresentedViewController:
(UIViewController *)presented
presentingViewController:(UIViewController *)presenting
sourceViewController:(UIViewController *)source {
MyPresentationController* myPresentation = [[MyPresentationController]
initWithPresentedViewController:presented presentingViewController:presenting];
return myPresentation;
}
4.4 適應(Adapting)不同的尺寸類(Size Classes )##
當彈出在屏幕上時,底層特征或容器視圖尺寸發生變化時,UIKit通知彈出控制器。這些變化通常發生在設備旋轉,但也可能在其它時候發生。可以使用特征和尺寸通知來調整彈出的自定義視圖,并更新彈出風格。
關于如何適應新特征和尺寸,請參考“創建自適應界面”。