翻译:iOS视图控制器编程指南(十一)——创建自定义presen
UIKit将视图控制器的内容与内容在屏幕上显示和消失的方式分割开来。底层present控制器对象管理Presented视图控制器,该对象管理显示视图控制器视图的视觉风格。present控制器可能会执行以下操作:
- 设置presented视图控制器的大小。
- 添加自定义视图改变presented内容的视觉外观。
- 为自定义视图提供过渡动画。
- 当app环境发生变化时,调整present的视觉外观。
UIKit为present控制器提供了标准present风格。当你设置一个视图控制器的present风格为UIModalPresentationCustom
,并提供一个适当的过渡代理,UIKit使用自定义present控制器。
自定义present过程
当你present视图控制器的present风格是UIModalPresentationCustom,UIKit查找一个自定义present控制器来管理present过程。随着present推进,UIKit调用present控制器的方法,建立自定义视图并渲染到适当的位置。
present控制器与其他动画对象一起实现整体过渡。动画对象渲染视图控制器的内容到屏幕上,present控制器处理其他一切。通常情况下,present控制器渲染自己的视图,但也可以覆盖present控制器的 presentedView方法,并让动画对象渲染这些视图。
在present过程中,UIKit:
1.调用过渡代理的 presentationControllerForPresentedViewController:presentingViewController:sourceViewController: 方法检索自定义present控制器
2.调用过渡代理的动画和交互式动画对象,如果有的话
3.调用present控制器的 presentationTransitionWillBegin方法
该方法的实现应该添加自定义视图到视图层级结构中并配置这些视图的动画。
4.从present控制器获取 presentedView
该方法返回的视图由动画对象渲染到合适的位置。正常情况下,该方法返回presented视图控制器的根视图。present控制器可以用自定义背景视图替换该视图。如果你确实指定一个不同的视图,你必须将presented视图控制器的根视图添加到视图层级结构中。
5.执行过渡动画
过渡动画包括动画对象创建的主要动画和其他配置与主动画一起运行的动画。关于过渡动画的更多信息,参见过渡动画序列( The Transition Animation Sequence)。
在动画过程中,UIKit调用present控制器的 containerViewWillLayoutSubviews和containerViewDidLayoutSubviews方法,这样你可以根据需要调整自定义视图的布局。
6.当过渡动画完成时,调用 presentationTransitionDidEnd: 方法。
在dismiss过程中,UIKit:
1.从当前的可见视图控制器获取自定义present控制器
2.调用过渡代理的动画和交互式动画对象,如果有的话
3.调用present控制器的 dismissalTransitionWillBegin方法
4.从present控制器获取 presentedView
5.执行过渡动画
过渡动画包括动画对象创建的主要动画和其他配置与主动画一起运行的动画。关于过渡动画的更多信息,参见过渡动画序列( The Transition Animation Sequence)。
动画过程中,UIKit调用present控制器的 containerViewWillLayoutSubviews和containerViewDidLayoutSubviews方法,这样就可以删除自定义约束。
6.当过渡动画完成,调用dismissalTransitionDidEnd: 方法
在present过程中,present控制器的frameOfPresentedViewInContainerView和presentedView
方法可能会调用多次,所以必须很快的返回实现。另外,presentedView
方法的实现不应该设置视图层级。视图层级结构应该在调用该方法时就配置好了。
创建自定义present控制器
实现自定义present更改,继承UIPresentationController 并添加代码创建present的视图和动画。当创建一个自定义present控制器,考虑以下问题:
- 你想添加什么视图?
- 你希望如何渲染额外视图到屏幕上?
- present视图控制器的大小?
- present如何适应水平常规和水平紧凑大小类?
- present视图控制器的视图在present完成时是否要删除?
所有这些决定需要覆盖UIPresentationController
类的不同方法。
设置presented视图控制器的frame
可以修改present视图控制器的frame,这样可以填充可用空间的一部分。默认情况下,present视图控制器的大小完全填补容器视图的frame。为了改变frame,覆盖present视图的frameOfPresentedViewInContainerView方法。类别11-1展示了只覆盖容器视图控制器右半部分的例子。在这种情况下,present控制器使用背景模糊视图覆盖容器的另一半。
列表11-1 改变present视图控制器的frame
<pre><code>
-(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;
}
</pre></code>
管理和渲染自定义视图
自定义present通常涉及添加自定义视图到presented内容上。使用自定义视图来实现纯粹视觉装饰或者是用他们添加实际行为到present上。例如,背景视图可以将手势识别器跟踪在presented内容范围以外的特定动作。
present控制器负责创建和管理所有与present相关的自定义视图。通常,在present控制器的初始化方法中创建自定义视图。在列表11-2中展示了自定义视图控制器的初始化方法,该视图控制器创建其自己的模糊视图。该方法创建视图并执行一些配置。
列表11-2 初始化present控制器
<pre><code>
-(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;
}
</pre></code>
使用 presentationTransitionWillBegin方法渲染自定义视图到屏幕上。在该方法中,配置自定义视图并添加他们到容器视图中,如列表11-3所示。使用过度协调器的presented或presenting视图控制器来创建动画。在该方法中不要修改presented视图控制器的视图。动画对象负责渲染Presented视图控制器到 frameOfPresentedViewInContainerView 方法返回的frame上。
列表11-3 渲染模糊视图到屏幕上
<pre><code>
-(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];
}
}
</pre></code>
在present结束后,使用 presentationTransitionDidEnd: 方法处理取消present产生的清理。如果不满足阈值条件,交互式动画对象可能取消过渡。当这种情况发生时,UIKit调用 presentationTransitionDidEnd:
方法并设置为NO。当发生取消时,删除在present之前添加的任何自定义视图并返回之前配置的其他视图,如列表11-4所示。
11-4 处理取消present
<pre><code>
-(void)presentationTransitionDidEnd:(BOOL)completed {
// If the presentation was canceled, remove the dimming view.
if (!completed)
[self.dimmingView removeFromSuperview];
}
</pre></code>
当dismiss 视图控制器,使用 dismissalTransitionDidEnd:方法从视图层级结构中删除自定义视图。如果希望视图消失有动画,在 dismissalTransitionDidEnd:方法中设置动画。列表11-5 展示了删除模糊视图的两种方法。总是检查 dismissalTransitionDidEnd:方法的参数,了解dismiss是否成功或取消。
列表11-5 dismiss present视图
<pre><code>
-(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];
}
</pre></code>
vend present控制器到UIKit
当present视图控制器,执行以下步骤:
- 设置presented视图控制器的modalPresentationStyle属性为
UIModalPresentationCustom
。 - 指定presented视图控制器的 transitioningDelegate属性为过渡代理。
- 实现过渡代理的presentationControllerForPresentedViewController:presentingViewController:sourceViewController:方法。
当过渡代理需要present控制器,UIKit调用过渡代理的presentationControllerForPresentedViewController:presentingViewController:sourceViewController:
方法。该方法的实现如列表11-6一样简单。创建present控制器,配置并返回。如果该方法返回nil,UIKit使用全屏present风格present该视图控制器。
列表11-6 创建自定义present控制器
<pre><code>
-(UIPresentationController *)presentationControllerForPresentedViewController:
(UIViewController *)presented
presentingViewController:(UIViewController *)presenting
sourceViewController:(UIViewController *)source {
MyPresentationController* myPresentation = [[MyPresentationController]
initWithPresentedViewController:presented presentingViewController:presenting];
return myPresentation;
}
</pre></code>
采用不同大小类
当present在屏幕上,当底层trait或容器视图的大小发生变化,UIKit通知present控制器。这种变化通常发生在设备旋转的情况下。在合适的时候,可以使用trait和大小通知来适配present自定义视图和更新present风格。
关于如何采用trait和大小的信息,参见构建自适应界面( Building an Adaptive Interface)。