[TOC=5]
* * * * *
>原文鏈接 :[Creating Custom Presentations](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/DefiningCustomPresentations.html#//apple_ref/doc/uid/TP40007457-CH25-SW1)
UIKit 將視圖控制器的內容與內容呈現、顯示在屏幕上的方式分開。被呈現的視圖控制器由底層的呈現控制器對象管理,該對象用于管理顯示視圖控制器視圖的視覺樣式。呈現控制器可以執行以下操作:
* 設置被呈現的視圖控制器的大小。
* 添加自定義視圖來更改被呈現內容的視覺外觀。
* 為其自定義視圖提供轉換動畫。
* 當應用程序環境發生更改時,調整呈現的外觀。
UIKit 為標準呈現樣式提供了呈現控制器。 當將視圖控制器的呈現樣式設置為 [UIModalPresentationCustom](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/1621375-custom) 并提供適當的轉換代理時,UIKit 會改為使用自定義呈現控制器。
### 第一節 自定義呈現過程
當呈現其呈現樣式為 [UIModalPresentationCustom](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/1621375-custom) 的視圖控制器時,UIKit 會查找自定義呈現控制器來管理呈現過程。 隨著呈現的進行,UIKit 會調用呈現控制器的方法,使其有機會設置任何自定義視圖并將其設置到位。
呈現控制器與動畫器對象一起工作來實現整體轉換。動畫器對象將視圖控制器的內容動畫化到屏幕上,呈現制器處理所有其他事情。通常情況下,呈現控制器會為其自己的視圖設置動畫,但也可以重寫呈現控制器的 [presentedView](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618321-presentedview) 方法,并讓動畫器對象為所有或部分視圖設置動畫效果。
在呈現期間,UIKit:
1. 調用轉換代理的 [presentationControllerForPresentedViewController:presentingViewController:sourceViewController:](https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate/1622057-presentationcontroller) 方法來獲取自定義呈現控制器。
2. 如果有的話,訪問轉換代理獲取動畫器和交互式動畫器對象。
3. 調用呈現控制器的 [presentationTransitionWillBegin](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618330-presentationtransitionwillbegin) 方法;
此方法的實現應將自定義視圖添加到視圖層次結構中,并為這些視圖配置動畫。
4. 從呈現控制器獲取 [presentedView](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618321-presentedview) ;
由該方法返回的視圖由動畫器對象動畫到目標位置。 通常,這個方法返回被呈現的視圖控制器的根視圖。 呈現控制器可以根據需要使用自定義背景視圖替換該視圖。 如果指定了不同的視圖,則必須將被呈現的視圖控制器的根視圖嵌入視圖層次結構中。
5. 執行轉換動畫;
轉換動畫包括由動畫器對象創建的主要動畫以及您配置與主要動畫一起運行的動畫。 有關轉換動畫的信息,請參閱 [The Transition Animation Sequence](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html#//apple_ref/doc/uid/TP40007457-CH16-SW2) 。
在動畫過程中,UIKit 調用呈現控制器的 [containerViewWillLayoutSubviews](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618341-containerviewwilllayoutsubviews) 和 [containerViewDidLayoutSubviews](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618331-containerviewdidlayoutsubviews) 方法,以便根據需要調整自定義視圖的布局。
6. 轉換動畫結束時調用 [presentationTransitionDidEnd:](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618327-presentationtransitiondidend) 方法。
在關閉期間,UIKit:
1. 從當前可見的視圖控制器中獲取自定義呈現控制器
2. 如果有的話,訪問轉換代理獲取動畫器和交互式動畫器對象。
3. 調用呈現控制器的 [dismissalTransitionWillBegin](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618342-dismissaltransitionwillbegin) 方法;
此方法的實現應將自定義視圖添加到視圖層次結構中,并為這些視圖配置動畫。
4. 從呈現控制器獲取已呈現視圖 [presentedView](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618321-presentedview) ;
5. 執行轉換動畫;
轉換動畫包括由動畫器對象創建的主要動畫以及您配置與主要動畫一起運行的動畫。 有關轉換動畫的信息,請參閱 [The Transition Animation Sequence](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html#//apple_ref/doc/uid/TP40007457-CH16-SW2) 。
在動畫過程中,UIKit 調用呈現控制器的 [containerViewWillLayoutSubviews](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618341-containerviewwilllayoutsubviews) 和 [containerViewDidLayoutSubviews](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618331-containerviewdidlayoutsubviews) 方法,以便根據需要調整自定義視圖的布局。
6. 轉換動畫結束時調用 [dismissalTransitionDidEnd:](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618323-dismissaltransitiondidend) 方法。
在呈現過程中,呈現控制器的 [frameOfPresentedViewInContainerView](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618337-frameofpresentedviewincontainerv) 和 [presentedView](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618321-presentedview) 方法可能會被多次調用,因此自定義實現應該很快返回。 另外, [presentedView](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618321-presentedview) 方法的實現不應該嘗試設置視圖層次結構。 視圖層次結構應該已經在調用此方法的時候配置好了。
### 第二節 創建自定義呈現控制器
要實現自定義呈現樣式,請使用 [UIPresentationController](https://developer.apple.com/documentation/uikit/uipresentationcontroller) 的子類,并添加代碼來為呈現創建視圖和動畫。 創建自定義呈現控制器時,請考慮以下問題:
* 希望添加什么視圖?
* 額外視圖如何在屏幕上做動畫?
* 被呈現的視圖控制器應該是多大?
* 如何適配水平空間正常和緊湊?
* 呈現完成后,是否應刪除被呈現視圖控制器的視圖?
所有這些決定都要求重寫 [UIPresentationController](https://developer.apple.com/documentation/uikit/uipresentationcontroller) 類的不同方法。
#### 2.1 設置被呈現的視圖控制器的 frame
可以修改顯示的視圖控制器的 frame,使它只填充可用空間的一部分。默認情況下,被呈現的視圖控制器的大小可以完全填充容器視圖的框架。要更 frame,請重寫呈現控制器的 frameOfPresentedViewInContainerView 方法。清單 11-1 顯示了一個例子,其中 frame 被改變為只覆蓋容器視圖的右半部分。在這種情況下,呈現控制器使用背景蒙板視圖來覆蓋容器的另一半。
###### 清單 11-1 改變被呈現的視圖控制器的 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;
}
~~~
#### 2.2 管理和動畫自定義視圖
自定義的呈現通常涉及向被呈現的內容添加自定義視圖。使用自定義視圖來實現純粹的視覺裝飾,或者使用它們來為呈現添加實際的操作行為。例如,背景視圖可能會包含手勢識別器來跟蹤顯示內容的邊界之外的特定操作。
呈現控制器負責創建和管理與它的表示相關聯的所有自定義視圖。通常在呈現控制器的初始化過程中創建自定義視圖。清單 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](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618330-presentationtransitionwillbegin) 方法將自定義視圖動畫到屏幕上。在這個方法中,配置自定義視圖并將它們添加到容器視圖中,如清單 11-3 所示。使用被呈現或呈現視圖控制器的轉換協調器來創建動畫。不要在這個方法中修改被呈現的視圖控制器的視圖。動畫器對象負責將被呈現的視圖控制器動畫到從 [frameOfPresentedViewInContainerView](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618337-frameofpresentedviewincontainerv) 方法返回的 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:](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618327-presentationtransitiondidend) 方法來處理由取消呈現所需要進行的清理工作。如果不滿足閾值條件,交互式動畫器對象可能會取消轉換。發生這種情況時,UIKit 會將參數 `NO` 傳遞給 [presentationTransitionDidEnd:](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618327-presentationtransitiondidend)方法。發生取消時,請刪除在呈現開始處添加的任何自定義視圖,并將其他視圖返回到其先前的配置,如清單 11-4 所示。
###### 清單 11-4 處理已取消的呈現
~~~
- (void)presentationTransitionDidEnd:(BOOL)completed {
//如果呈現取消移除蒙板視圖(If the presentation was canceled, remove the dimming view.)
if (!completed)
[self.dimmingView removeFromSuperview];
}
~~~
當視圖控制器被關閉時,使用 [dismissalTransitionDidEnd:](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618323-dismissaltransitiondidend) 方法從視圖層次結構中刪除自定義視圖。如果要實現視圖的消失動畫效果,請在 [dismissalTransitionDidEnd:](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618323-dismissaltransitiondidend) 方法中設置這些動畫。清單 11-5 顯示了在前面的例子中去除蒙板視圖的兩種方法的實現。
檢查 [dismissalTransitionDidEnd:](https://developer.apple.com/documentation/uikit/uipresentationcontroller/1618323-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];
}
~~~
### 第三節 把呈現控制器傳給 UIKit
在呈現視圖控制器時,請執行以下操作以使用自定義呈現控制器來顯示它:
* 將被呈現視圖控制器的 [modalPresentationStyle](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621355-modalpresentationstyle) 屬性設置為 [UIModalPresentationCustom](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/1621375-custom) 。
* 將轉換代理賦值給被呈現視圖控制器的 [transitioningDelegate](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621421-transitioningdelegate) 屬性。
* 實現轉換代理的 [presentationControllerForPresentedViewController:presentingViewController:sourceViewController:](https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate/1622057-presentationcontroller) 方法。
UIKit 調用轉換代理的 [presentationControllerForPresentedViewController:presentsViewController:sourceViewController:](https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate/1622057-presentationcontroller) 方法獲取呈現控制器。這個方法的實現應該像清單 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;
}
~~~
### 第四節 適配
在屏幕上顯示時,當底層特征發生變化或容器視圖大小變化時,UIKit 會通知呈現控制器。 這種改變通常發生在設備旋轉期間,但是可能在其他時間發生。 可以使用特征和大小通知來調整呈現的自定義視圖,并根據需要更新呈現樣式。
有關如何適配的信息,請參閱 [Building an Adaptive Interface](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/BuildinganAdaptiveInterface.html#//apple_ref/doc/uid/TP40007457-CH32-SW1) 。