UIViewController(五):创建自定义 Presentation Controller

UIKit将视图控制器的内容与内容被呈现和显示在屏幕上的方式分开。呈现的视图控制器由底层的presentation controller对象管理,该对象管理用于显示视图控制器的视图的视觉样式。presentation controller可以执行以下操作:

  • 设置所呈现的视图控制器的尺寸。
  • 添加自定义视图来更改所呈现内容的视觉外观。
  • 为其任何自定义视图提供转场动画。
  • 当应用程序的屏幕环境发生变化时,调整所呈现内容的视觉外观。

UIKit为标准呈现样式提供了presentation controller,当将视图控制器的呈现样式设置为UIModalPresentationCustom并提供合适的转场动画委托时,UIKit会改为使用我们自定义的presentation controller。

自定义呈现的过程

当呈现一个呈现样式为UIModalPresentationCustom的视图控制器时,UIKit会查看一个自定义presentation controller来管理呈现过程。随着呈现的进行,UIKit会调用presentation controller的方法,使其有机会设置任何自定义视图并将视图动画到某个位置。

presentation controller与任何animator对象一起工作来实现整体转场。animator对象将视图控制器的内容动画显示到屏幕上,而presentation controller处理所有其他事情。通常情况下,自定义presentation controller会为其自己的视图创建动画。但是我们也可以覆写presentation controller的presentedView方法,让animator对象为所有或者部分视图创建动画。

在呈现过程中,UIKit:

  1. 调用转场动画委托对象的presentationControllerForPresentedViewController:presentingViewController:sourceViewController:方法来检索我们自定义的presentation controller。
  2. 如果存在animator对象或者交互式animator对象的话,会向转场动画委托对象请求获取它们。
  3. 调用自定义presentation controller的presentationTransitionWillBegin方法。在该方法的实现中,应该将任何自定义视图添加到视图层次结构中,并为这些视图创建动画。
  4. 调用presentation controller的presentedView方法获取需要呈现的视图。presentedView方法返回的视图由animator对象为其创建动画。通常情况下,该方法返回需要呈现的视图控制器的根视图。自定义presentation controller可以根据需要来决定是否使用自定义背景视图来替换该视图。如果确实指定了不同的视图,则必须将需要呈现的视图控制器的根视图嵌入到presentation controller的视图层次结构中。
  5. 执行转场动画。转场动画包括animator对象创建的主要动画以及我们配置的与主要动画一起执行的任何动画。在动画过程中,UIKit会调用presentation controller的containerViewWillLayoutSubviewscontainerViewDidLayoutSubviews方法,以便我们可以根据需要调整自定义视图的布局。
  6. 在转场动画结束时,调用presentation controller的presentationTransitionDidEnd:方法。

在移除过程中,UIKit:

  1. 从呈现的视图控制器中获取我们自定义的presentation controller。
  2. 如果存在animator对象或者交互式animator对象的话,会向转场动画委托对象请求获取它们。
  3. 调用presentation controller的dismissalTransitionWillBegin方法。在该方法的实现中,应该将任何自定义视图添加到视图层次结构中,并为这些视图创建动画。
  4. 调用presentation controller的presentedView方法获取已经呈现的视图。
  5. 执行转场动画。转场动画包括animator对象创建的主要动画以及我们配置的与主要动画一起执行的任何动画。在动画过程中,UIKit会调用presentation controller的containerViewWillLayoutSubviewscontainerViewDidLayoutSubviews方法,以便我们能够删除任何自定义约束。
  6. 在转场动画结束时,调用presentation controller的dismissalTransitionDidEnd:方法。

在呈现过程中,presentation controller的frameOfPresentedViewInContainerViewpresentedView方法可能会被多次调用,因此这两个方法的实现应该尽量简单。另外,在presentedView方法的实现中不应该尝试去设置视图层次结构。在调用该方法时,应该已经设置好视图层次结构。

创建自定义presentation Controller

要实现自定义呈现样式,请子类化UIPresentationController并添加代码为自定义呈现创建视图和动画。创建自定义presentation Controller时,请考虑以下问题:

  • 想添加哪些视图。
  • 想要屏幕上的视图执行怎样的动画效果。
  • 呈现的视图控制器的尺寸应该是多少。
  • 呈现的内容应该如何在水平正常和水平紧凑的屏幕环境之间进行适应。
  • 呈现完成后,是否移除发起呈现的视图控制器的视图。

所有这些决定都要求覆写UIPresentationController类的不同方法。

设置呈现的视图控制器的Frame

可以修改呈现的视图控制器的frame,使其仅填充部分可用空间。默认情况下,呈现的视图控制器的尺寸能够完全填充容器视图。要更改frame,请覆写presentation Controller的frameOfPresentedViewInContainerView方法。以下代码显示了一个示例,其中呈现的视图控制器的大小被改为仅覆盖容器视图的右半部分。在这种情况下,presentation Controller使用背景调光视图来覆盖容器视图的另一半。

- (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;
}

管理和动画自定义视图

自定义呈现样式通常涉及向呈现的内容中添加自定义视图。使用自定义视图来实现纯粹的视觉装饰或者使用它们将实际行为添加到呈现中。例如,背景视图可能包含手势识别器来跟踪呈现内容边界之外的特定操作。

presentation Controller负责创建和管理与呈现有关的所有自定义视图。通常情况下,在presentation Controller的初始化过程中创建自定义视图。以下代码显示了创建自己的调光视图的自定义视图控制器的初始化方法,此方法创建视图并执行一些最小配置。

- (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方法将自定义视图动画显示到屏幕上。在此方法的实现中,配置自定义视图并将其添加到容器视图中,如下代码所示。使用发起呈现或者呈现的视图控制器的转场动画协调器来创建任何动画。切勿在此方法中修改呈现的视图控制器的视图。animator对象负责将呈现的视图控制器动画显示到frameOfPresentedViewInContainerView方法返回的frame去。

- (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:方法来处理由于取消呈现所导致的任何清理。如果不满足阀值条件,交互式animator对象可能会取消转场动画。发生这种情况时,UIKit会调用presentationTransitionDidEnd:方法并传递NO值给该方法。当发生取消转场动画操作时,删除在呈现开始时添加的任何自定义视图,并将其他视图还原为之前的配置,如下所示。

- (void)presentationTransitionDidEnd:(BOOL)completed
{
    // If the presentation was canceled, remove the dimming view.
    if (!completed)
        [self.dimmingView removeFromSuperview];
}

当视图控制器被移除时,使用dismissalTransitionDidEnd:方法从视图层次结构中删除自定义视图。如果想要视图动画消失,请在dismissalTransitionDidEnd:方法中配置这些动画。以下代码显示了在前面的例子中移除调光视图的两种方法的实现。始终检查dismissalTransitionDidEnd:方法的参数以确定移除是成功还是被取消。

- (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];
}

传递presentation Controller给UIKit

呈现一个视图控制器时,请执行以下操作来使用自定义presentation Controller显示视图控制器:

  • 将需要呈现的视图控制器的UIModalPresentationCustom属性设置为UIModalPresentationCustom
  • 给需要呈现的视图控制器的transitioningDelegate属性分配一个转场动画委托对象。
  • 实现转场动画委托对象的presentationControllerForPresentedViewController:presentingViewController:sourceViewController:方法。

当UIKit需要使用自定义presentation Controller时,会调用转场动画委托对象的presentationControllerForPresentedViewController:presentingViewController:sourceViewController:方法。该方法的实现应该以下代码那样简单,只需要创建自定义的presentation Controller,进行配置并返回。如果此方法返回nil,则UIKit会使用全屏呈现样式来呈现视图控制器。

- (UIPresentationController *)presentationControllerForPresentedViewController:
(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source
{
    MyPresentationController* myPresentation = [[MyPresentationController] initWithPresentedViewController:presented presentingViewController:presenting];

    return myPresentation;
}

适应不同的屏幕环境

在屏幕上呈现内容时,UIKit会在底层特征发生改变或者容器视图的尺寸发生变化时通知我们自定义的presentation Controller。这种变化通常发生在设备旋转过程中,但也可能发生在其他时间。可以使用trait和size通知来适当调整presentation Controller的自定义视图并更新为合适的呈现样式。

有关如何适应新的trait和size的信息,请参看Building an Adaptive Interface

Demo

Demo地址:https://github.com/Jen668/UIViewControllerDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容