ViewController编程指南

官方文档

ViewController的角色

视图控制器是应用程序内部的基础结构,每个应用程序App都必须至少有一个ViewController,把它拆分出来是View + Controller,分为视图和控制2个部分,
它提供了用户界面,以及提供用户界面与底层数据之间的交互,同时也可以在控制器中直接管理视图,如MVC架构,VC都集中在一起,所以ViewController的处理的逻辑非常多,因此它的地位也是非常重要的.

它的主要作用概括为如下几点

  • 定义视图和视图管理,通常结合Xib或者自定义View来构建视图
  • 切换视图控制器,不同的场景
  • 处理来自系统级别的事件,如应用程序派发的Event常规事件(如点击,内存警告,屏幕转向..)
    有两种不同的视图控制器
  • 内容视图控制器管理应用程序内容的一部分,是您创建的主要类型的视图控制器。
  • 容器视图控制器从其他视图控制器(称为子视图控制器)收集信息,并以便于导航或以不同方式显示这些视图控制器的内容的方式显示信息。

ViewController对View的管理

视图控制器的一个最重要的功能就是管理视图的层级,每个ViewController都包含一个Root view,root view包含视图控制器的所有内容。视图控制器始终具有对其根视图的引用,并且每个视图都具有对其子视图的强引用。

ViewController数据管理

  • 视图控制器充当它管理的视图和应用程序数据之间的中介
  • 在传统的MVC架构中,通过在ViewController中定义属性和方法来处理UI事件和完成和Storage(缓存),Services(系统服务),ApiCall(网络调用)之间的数据交互以及业务逻辑处理.
  • 视图控制器引用了用于展示界面的数据

ViewController用户交互行为管理

  • 视图控继承成于UIResponder,当可以响应应用程序发送的事件,并传递给它的根视图,
  • 当用户在屏幕点击之后,屏幕会通过传感器计算出触摸点屏幕的位置,经过处理后将触摸点为信息发送给IOKit,经过一系列转发之后,事件最终派发给当前运行的应用程序AppDelegate,然后再逐一向下分发,通过hitTest:withEvent:查找合适的view,
  • 如果最后没有找到的合适的view,或者找到了view没有处理该事件(处理该事件只需要重写UIResponder系列的事件响应方法),那事件就会原路返回

ViewController资源管理

  • 数据引用上的管理,对于subView的创建,避免重复引用子视图,通过weak引用,这样自视图被释放后就不会占用内存空间
  • 对于用户界面需要处理的数据,则申明成属性和ViewController关联,随着ViewController的销毁而释放或者解除它们的引用
  • 当ViewController接收到系统didReceiveMemoryWarning事件后,要减少无用的数据
  • 将页面长期不需要的数据进行按需加载,缓存到磁盘
  • 实际开发中对于ViewController的资源的管理主要是通过逻辑复用,数据分级缓存,懒加载,压缩,异步加载,LRU等策略管理资源,合理的获取资源,持有,释放资源,有助于减少应用程序的CPU开销,改善界面渲染性能,提升用户体验。

ViewController的适应性

  • ViewController负责视图的展示,和底层环境变化的适配,在不同的设备,如iPadiPhone设备,他们的设备尺寸变换都是比较大的,为了避免重复的代码,我们可以根据机型以及屏幕尺寸对View进行适配(size class可以提供为视图提供多套布局,或者手动管理创建不同的布局).

ViewController的继承体系

  • View Controller Hierarchy: 视图控制器的关系定义了每个视图控制器的行为,UIKit希望开发者使用指定的视图控制器,维护正确的视图控制器的关系结构可以确保系统将正确的行为传递给视图控制器,如果打破这种关系约束,我们需要格外注意,可能会有部分功能停止运行。
  • The Root View Controller: 根视图控制器是视图控制器层次结构的锚点。每个窗口都有一个根视图控制器,其内容填充该窗口。根视图控制器定义用户看到的初始内容。图中显示了根视图控制器和窗口之间的关系。因为窗口本身没有可见内容,所以视图控制器的视图提供所有内容。

  • Container View Controllers: 容器控制器可以让我们组装复杂界面,并易于管理和维护,容器视图控制器将一个或多个子视图控制器的内容与可选的自定义视图混合在一起,以创建其最终界面. 例如UINavigationController从它的子视图控制器中展示内容,并搭配其自带的NavigationBar和一个可选的Toolbar.UIKit框架包括多个容器试图控制器,如UISpitViewController,UIPageViewController.

ViewController容器创建

  • Implementing a Container View Controller: 实现一个容器试图控制器分以下2个步骤,

    • 添加对子控制器的管理权,
    • 添加子视图到自身的视图上,或设置为自身的视图
    • 下面是Xcode通过storage创建一个容器试图控制器
      - 通过代码来创建容器试图控制器

       - (void) displayContentController: (UIViewController*) content {
      [self addChildViewController:content];
      content.view.frame = [self frameForContentController];
      [self.view addSubview:self.currentClientView];
      [content didMoveToParentViewController:self];
      }
      - (void) hideContentController: (UIViewController*) content {
      [content willMoveToParentViewController:nil];
      [content.view removeFromSuperview];
      [content removeFromParentViewController];
      }
      ```
  • 在容器控制器中控制子控制器的专场

    ```Objective-C
    - (void)cycleFromViewController: (UIViewController*) oldVC
    toViewController: (UIViewController*) newVC {

    // Prepare the two view controllers for the change.
    [oldVC willMoveToParentViewController:nil];
    [self addChildViewController:newVC];

    // Get the start frame of the new view controller and the end frame
    // for the old view controller. Both rectangles are offscreen.
    newVC.view.frame = [self newViewStartFrame];
    CGRect endFrame = [self oldViewEndFrame];

    // Queue up the transition animation.
    [self transitionFromViewController: oldVC toViewController: newVC
    duration: 0.25 options:0
    animations:{
    // Animate the views to their final positions.
    newVC.view.frame = oldVC.view.frame;
    oldVC.view.frame = endFrame;
    }
    completion:BOOL finished {
    // Remove the old view controller and send the final
    // notification to the new view controller.
    [oldVC removeFromParentViewController];
    [newVC didMoveToParentViewController:self];
    }];
    }
    ```

    • 容器试图控制器的设计原则
      • 尽可能的设计简洁,只访问childViewController: 而不应该访问它的视图或其它属性
      • 子控制器之间的交互应该尽可能少,或者不联系,如果容器控制器需的行为需要印象子控制器,仅可能的提供一个代理
      • 首先使用常规视图设计容器,使用常规视图(而不是来自子视图控制器的视图)使您有机会在简化的环境中测试布局约束和动画转换。当常规视图按预期工作时,将其替换为子视图控制器的视图。
    • 将部分控制权委派给子视图控制器
      • 如让子视图控制器确定状态栏样式: 若要将状态栏外观委派给子级,请重写容器视图控制器中的一个或两childViewControllerForStatusBarStylechildViewControllerForStatusBarHidden方法。
      • 让子视图控制器设定自己的大小: 如设置preferredContentSize来适配子控制器是图的布局

Apple ViewController的设计提示

许多iOS框架定义了视图控制器,可以在应用程序中按原样使用。使用这些系统提供的视图控制器可以为您节省时间,并确保为用户提供一致的体验。

大多数系统视图控制器都是为特定任务而设计的。一些视图控制器提供对用户数据(如联系人)的访问。其他的则可以提供对硬件的访问,或者提供专门调整的接口来管理媒体。例如,UIKit中的UIImagePickerController类显示一个标准接口,用于捕获图片和视频以及访问用户的相机卷。

  • 尽可能使用系统提供的视图控制器,在某些情况下有用,缺点也显而易见,不利于新需求的开发,定制化程度差
  • 在创建自己的自定义视图控制器之前,请查看现有的框架,以确定是否已经存在用于要执行的任务的视图控制器。
    • UIKit框架提供视图控制器,用于在iCloud上显示警报、拍摄图片和视频以及管理文件。UIKit还定义了许多可以用来组织内容的标准容器视图控制器。
    • GameKit框架为匹配玩家、管理排行榜、成就和其他游戏功能提供视图控制器。
    • 地址簿UI框架提供了视图控制器,用于显示和选择联系人信息。
    • MediaPlayer框架提供了用于播放和管理视频以及从用户库中选择媒体资产的视图控制器。
    • EventKitUI框架提供了用于显示和编辑用户日历数据的视图控制器。
    • GLKit框架提供了一个视图控制器来管理OpenGL渲染表面。
    • Multipeer连接框架提供视图控制器,用于检测其他用户并邀请他们进行连接。
    • 消息UI框架提供了用于撰写电子邮件和SMS消息的视图控制器。
    • PassKit框架提供视图控制器,用于显示传递并将它们添加到Passbook。
    • 社交框架提供视图控制器,用于为Twitter、Facebook和其他社交媒体网站撰写消息。
    • AVFoundation框架提供了一个视图控制器来显示媒体资产。
  • 重要: 从不要随便修改系统控制器的是图继承树,(目前已修改过UIImagePickerViewController和UIAlertViewController,暂未发现问题)
  • 保证ViewController的逻辑单一: 任何视图控制器都不应了解其他视图控制器的内部工作或视图层次结构,在两个视图控制器需要来回通信或传递数据的情况下,它们应该始终使用显式定义的公共接口进行通信,多使用代理来实现不同控制器的通信.
  • 将视图控制器的根视图单独用作其余内容的容器: 将根视图用作容器可以为所有视图提供一个公共父视图,这使得许多布局操作更加简单。许多自动布局约束需要一个公共父视图来正确布局视图。
  • 知道你的数据在哪里: 视图控制器可以在临时变量中存储一些数据并执行一些验证,但它的主要职责是确保其视图包含准确的信息, 数据对象负责管理实际数据并确保该数据的整体完整性。
  • 适应变化: 使用内置的自适应支持来响应视图控制器中的大小和大小类更改,和约束来控制是图,避免绝对和固定布局,提高代码的复用性。(根据项目需要配置)

定义ViewController的子类

  • 对于内容视图控制器,最常见的父类如下:
    • 当视图控制器的主视图是表时,请特别使用UITableViewController。
    • 当视图控制器的主视图是集合视图时,请特别使用UICollectionViewController。
    • 对所有其他视图控制器使用UIViewController。
  • 处理用户交互事件,
    • 处理action事件: UIControl系列控件的TargetAction事件,Gesture事件
    • 处系统理通知事件: notification, remote events
    • 作为delegate处理业务逻辑和数据
  • Runtime时期定义是图
    • 根据storyboard信息初始化视图
    • 连接所有的outletsactions
    • 绑定controllerview属性
    • 调用controllerawakeFromNib方法
    • 调用viewDidLoad方法,视图初始化完毕
    • 在视图将要展示的屏幕之前,可以对是图进行加工,如applyViewStyle,setupViewLayout,bindViewActions,调用viewWillAppear:方法
    • 更新视图layout
    • 展示视图到屏幕上
    • 调用viewDidAppear:
    • 添加、删除或修改视图的大小或位置时,请记住添加和删除应用于这些视图的任何约束。对视图层次结构进行与布局相关的更改会导致UIKit将布局标记为脏的。在下一个更新周期中,布局引擎使用当前布局约束计算视图的大小和位置,并将这些更改应用于视图层次结构。
  • 管理是图的布局,当视图的约束变换之后触发,或者手动标记setNeedsLayout
    • call viewWillLayoutSubviews
    • call containerViewWillLayoutSubviews如果是UIPresentationController类型控制器
    • call layoutSubviews
    • 应用布局信息到视图上
    • call viewDidLayoutSubviews
    • call containerViewDidLayoutSubviews如果是UIPresentationController类型控制器
  • 布局的注意事项

    • 尽量使用Use Auto Layout
    • 充分利用利用顶部和底部的布局指南
    • 请记住在添加或删除视图时更新约束
    • 在为视图控制器的视图设置动画时临时删除约束: 使用UIKit核心动画设置视图动画时,请删除动画期间的约束,并在动画完成后将其添加回来。如果视图的位置或大小在动画过程中发生了更改,请记住更新约束。
  • 有效的管理内存

    • 在控制器的初始化方法init..中,初始化重要的数据
    • viewDidLoad初始化视图需要加载的数据,
    • 处理低内存警告通知didReceiveMemoryWarning,尽可能多的释放不需要的数据
    • dealloc中释放不需要的资源,如关闭音视频捕捉的session,或某些全局单例的轮询事件(不需要再使用的)

ViewController支持辅助访问

  • Moving the VoiceOver Cursor to a Specific Element
  • Responding to Special VoiceOver Gestures
  • Observing Accessibility Notifications

ViewController保存和存储状态

  • 视图控制器在状态保持和恢复过程中起着重要的作用。状态保留记录应用程序挂起前的配置,以便在后续应用程序启动时恢复配置。将应用程序返回到以前的配置可以为用户节省时间,并提供更好的用户体验。
  • 保存和恢复过程基本上是自动的,但是你需要告诉iOS你的应用程序的哪些部分要保留。保存应用程序的视图控制器的步骤如下:
    • (必需)将恢复标识符分配给要保留其配置的视图控制器
    • (必需)告诉iOS如何在启动时创建或定位新的视图控制器对象
    • 对于每个视图控制器,存储将该视图控制器恢复到其原始配置所需的任何特定配置数据
  • 标记视图控制器以保存
    • UIKit只保留您让它保留的视图控制器。每个视图控制器都有一个restorationIdentifier属性,默认情况下其值为零。将该属性设置为有效字符串会告诉UIKit应该保留视图控制器及其视图。可以通过编程方式或在脚本文件中指定恢复标识符。
    • 分配恢复标识符时,请记住视图控制器层次结构中的所有父视图控制器也必须具有恢复标识符。在保存过程中,UIKit从窗口的根视图控制器开始并遍历视图控制器层次结构。如果该层次结构中的视图控制器没有恢复标识符,则视图控制器及其所有子视图控制器和显示的视图控制器将被忽略。
  • 在启动时恢复视图控制器
    • 如果视图控制器有一个恢复类,UIKit会要求该类提供视图控制器。UIKit调用具有restorationident的viewControllerWithRestorationIdentifierPath:编码器:用于检索视图控制器的关联还原类的方法。如果该方法返回nil,则假定应用程序不想重新创建视图控制器,UIKit停止查找它。
    • 如果应用程序控制器没有提供视图,则该应用程序将请求代理控制器没有提供视图。UIKit打电话给美联社应用程序:viewControllerWithRestorationIdentifierPath:coder:应用程序委托的方法,以查找不带还原类的视图控制器。如果该方法返回nil,UIKit将尝试隐式地查找视图控制器。
    • 如果具有正确还原路径的视图控制器已经存在,UIKit将使用该对象。如果您的应用程序在启动时创建视图控制器(通过编程方式或从情节提要加载它们),并且这些视图控制器具有恢复标识符,UIKit会根据它们的恢复路径隐式地找到它们。
    • 如果视图控制器最初是从脚本文件加载的,UIKit将使用保存的脚本信息来定位和创建它。UIKit将有关视图控制器的脚本的信息保存在恢复归档文件中。在恢复时,UIKit使用该信息来定位同一个脚本文件,并在通过任何其他方式找不到视图控制器的情况下实例化相应的视图控制器。
      + (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
                      coder:(NSCoder *)coder {
   MyViewController* vc;
   UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
   if (sb) {
      vc = (PushViewController*)[sb instantiateViewControllerWithIdentifier:@"MyViewController"];
      vc.restorationIdentifier = [identifierComponents lastObject];
      vc.restorationClass = [MyViewController class];
   }
    return vc;
}
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
   [super encodeRestorableStateWithCoder:coder];
 
   [coder encodeInt:self.number forKey:MyViewControllerNumber];
}
 
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
   [super decodeRestorableStateWithCoder:coder];
 
   self.number = [coder decodeIntForKey:MyViewControllerNumber];
}
  • 保存和恢复视图控制器的提示

    • 去除不希望保留所有视图控制器。
    • 避免在恢复过程中交换视图控制器类,
    • 状态保存系统希望您按预期使用视图控制器。

    • 这种视图状态恢复策略可以应用到很多场景,它是基于协议定制化的,根据触发条件的不同和处理对象职责的不同,可以实现多种功能.是一种典型的访问者设计模式。

ViewController展示

  • 有两种方法可以在屏幕上显示视图控制器:
    • 将其嵌入容器视图控制器或显示它,如UIModalPresentationPopover
    • 使用“直接演示”在当前视图控制器的顶部显示新的视图控制器。
  • 使用系统自带的Presentation和Transition Process,通过present方法就能直接实现,几种常见的转场方式,

  • 展示控制器

    • showViewController:sender: and showDetailViewController:sender: 方法为显示视图控制器提供了最自适应和最灵活的方式。这些方法让呈现视图控制器决定如何最好地处理表示。例如,容器视图控制器可能会将视图控制器合并为子级,而不是以模块方式呈现。默认行为以模式显示视图控制器。
    • presentViewController:animated:completion:方法始终以模式显示视图控制器。调用此方法的视图控制器可能最终不会处理表示,但表示始终是模态的。此方法适应水平紧凑环境的表示样式。

    • 实现

    • 在弹出窗口中显示视图控制器

      • 将视图控制器的preferredContentSize属性设置为所需的大小。
      • 使用关联的UIPopoverPresentationController对象设置popoverPresentationController对象,该对象可从视图控制器的popoverPresentationController属性访问。仅设置以下选项之一:
      • 将barButtonItem属性设置为条形按钮项。
      • 将sourceView和sourceRect属性设置为某个视图中的特定区域。
      • 定义转场容器,实现fromViewtoView的动画

ViewController中Segue的使用

  • 需要在storyboard中提前设置Segue,指定类型push/`pop
  • 在运行时修改segue的行为,常用于统计分析,数据设置
  • Unwind Segue实现原理: 就是当unWindSegue点击事件触发的时候,能找到一个响应该点击事件的controller,并展示这个controller,两步
    • button和exit连线
    • 在希望响应unSegue跳转的控制器实现unSegue方法- (IBAction)myUnwindAction:(UIStoryboardSegue*)unwindSegue

ViewController视图展示和转场概念

  • 在之前有必要认识下两个重要概念容易混淆的概念
    • presentingViewController: 调用present方法的控制器,在下
    • presentedViewController: presented被展示,作为present方法的参数,被推出的控制器,在上。
  • 过渡动画序列: 将视图控制器的内容变成另外一个是图控制器,通常有两种表现形式,展示和退出,(presentations & dismiss).
  • 实现转场动画需要定义很多的方法和属性。UIKit提供了转换中涉及的所有对象,并根据转场动画的效果和使用场景进行了拆分,通过采用合适的类可以方便的定义动画。
  • 转场代理: UIViewControllerTransitioningDelegate,根据动画的自定义程度我们可以一次选择实现它的几个属性方法.

    • Animator objects: UIViewControllerAnimatedTransitioning
    • Interactive animator objects: UIViewControllerInteractiveTransitioning,通可以通过构造一个UIPercentDrivenInteractiveTransition来使用
    • Presentation controller: 提供一个自定义的PresentationViewController转场容器控制器来实现转场,自定义程度高,需要设置控制器的modalPresentationStyleUIModalPresentationCustom

  • 转场上下文,提供了转场所需要的重要信息,containerView,fromView,toView,duration,以及是否为入场动画,通过这些基础信息再加上传递的动画参数就可以灵活的配置专场动画; 下图展示了专场上下文是如何工作的

  • 转场协调器: 对于内置转场和自定义转场,UIKit会创建一个转场协调器对象,以便于执行可能需要执行的任何额外动画, 除了视图控制器的表示和解除之外,当界面旋转或视图控制器的帧发生更改时,也可以发生转换。所有这些转换都表示对视图层次结构的更改.

ViewController转场动画的实现

  • step1: 实现controllerTranslationDelegate

            - (id<UIViewControllerAnimatedTransitioning>)
                animationControllerForPresentedController:(UIViewController *)presented
    presentingController:(UIViewController *)presenting
    sourceController:(UIViewController *)source {
    MyAnimator* animator = [[MyAnimator alloc] init];
    return animator;
    }
    • step2: 实现动画对象
    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    ///1. 获取转场视图, fromView和toView
    UIView *containerView = [transitionContext containerView];
    UIViewController *fromVC = [transitionContext
    viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext
    viewControllerForKey:UITransitionContextToViewControllerKey];
    UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
    ///2. 设置转场视图的初始大小
    CGRect containerFrame = containerView.frame;
    CGRect toViewStartFrame = [transitionContext initialFrameForViewController:toVC];
    CGRect toViewFinalFrame = [transitionContext finalFrameForViewController:toVC];
    CGRect fromViewFinalFrame = [transitionContext finalFrameForViewController:fromVC];
    ///3. 判断转场视图的是否为入场,并设置转场结束时的位置,大小
    if (self.presenting) {
    toViewStartFrame.origin.x = containerFrame.size.width;
    toViewStartFrame.origin.y = containerFrame.size.height;
    }
    else {
    fromViewFinalFrame = CGRectMake(containerFrame.size.width,
    containerFrame.size.height,
    toView.frame.size.width,
    toView.frame.size.height);
    }
    ///4. 将toView添加到`containerView`中,预备显示
    [containerView addSubview:toView];
    toView.frame = toViewStartFrame;
    ///5. 执行转场动画
    [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];
    /// 转仓失败移则还原,移除toView
    if ((self.presenting && !success) || (!self.presenting && success)) {
    [toView removeFromSuperview];
    }
    ///6. ***通知转场上下文,结束转场***
    [transitionContext completeTransition:success];
    }];

}

  - step3: 为动画添加手势交互功能,此处直接创建了一个手势添加到`ContainerView`上,避免了直接从子视图获取手势带来的数据传递不够优雅的问题,但是却引用了`context`。
  
  ```Objective-C
  - (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];
}

-(void)handleSwipeUpdate:(UIGestureRecognizer *)gestureRecognizer {
    UIView* container = [self.contextData containerView];
 
    if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
        ///1. 初始化交互转场
        [self.panGesture setTranslation:CGPointMake(0, 0) inView:container];
    }
    else if (gestureRecognizer.state == UIGestureRecognizerStateChanged) {
        ///2. 实时获取手势位置用于计算交互进度
        CGPoint translation = [self.panGesture translationInView:container]; 
        CGFloat percentage = fabs(translation.y / CGRectGetHeight(container.bounds));
 
        ///3. 将手势移动的物理距离转化为动画进度
        [self updateInteractiveTransition:percentage];
    }
    else if (gestureRecognizer.state >= UIGestureRecognizerStateEnded) {
        ///4. 手势停止时执行完成交互式动画, 此处也可以设置一个区间值,如大于0.7直接完成动画,小于0.2重置动画

        [self finishInteractiveTransition];
        [[self.contextData containerView] removeGestureRecognizer:self.panGesture];
    }
}
  ```

## ViewController展示动画
[https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/DefiningCustomPresentations.html#//https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/DefiningCustomPresentations.html#//apple_ref/doc/uid/TP40007457-CH25-SW1apple_ref/doc/uid/TP40007457-CH25-SW1]()

- step1: 设置将要被展示的控制器是图的大小和初始化`Custom PresentationController`

```Objective-C
- (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;
}
- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController
                    presentingViewController:(UIViewController *)presentingViewController {
    self = [super initWithPresentedViewController:presentedViewController
                         presentingViewController:presentingViewController];
    if(self) {
        ///1. 可以附加点击事件关闭,如popup弹框
        self.dimmingView = [[UIView alloc] init];
        [self.dimmingView setBackgroundColor:[UIColor colorWithWhite:0.0 alpha:0.4]];
        [self.dimmingView setAlpha:0.0];
    }
    return self;
}
```

- step2: 管理自定义视图并设置其动画
```
- (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];
    }
}

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

- step3: 处理presentation结束事件

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

- step4: 在`TranslationDelegate`中返回`UIPresentationController`

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

## 其它一些特殊的转场参考,如collection视图present动画
[RMPZoomTransitionAnimator](https://github.com/recruit-mp/RMPZoomTransitionAnimator)
[PinterestAnimator](https://github.com/xhzengAIB/PinterestAnimator.git
) 

## 待续.
原文地址:https://www.cnblogs.com/wwoo/p/viewcontroller-bian-cheng-zhi-nan.html