[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) 。