ViewController Programming Guide 笔记(六)

Customizing the Transition Animations

过渡动画提供有关对应用程序界面更改时的可视反馈。UIKit 提供一组标准过渡样式给呈现时视图控制器使用,你可以提供自定义的转场动画

一、The Transition Animation Sequence

转场动画分为 presentationsdismissals,你可以自定义动画效果

1.The Transitioning Delegate

The transitioning delegate 是转场动画和自定义 presentations 的起点,他的工作是为 UIKit 提供以下对象:

  • Animator objects. 负责创建反转或隐藏 VC 的动画,遵循 UIViewControllerAnimatedTransitioning 协议
  • Interactive animator objects.
    • 交互式动画使用触摸事件或手势识别器来驱动(控制)自定义交互动画, UIViewControllerAnimatedTransitioning 协议
    • 最简单的使用方式是创建 UIPercentDrivenInteractiveTransition 的子类,然后在该子类中添加 event-handling code. 这个类已经提供了一些 animator objects 供使用
  • Presentation controller. 该对象管理着 presentation style(样式),你可以对样式进行自定义

使用的时候将 presentedViewController 的 transitioningDelegate 属性设置为 transitioning delegate 对象即可。

2.The Custom Animation Sequence

当上一步的 transitioningDelegate 被设置了之后,UIKit 会调用

func animationControllerForPresentedController(_ presented: UIViewController,  
                presentingController presenting: UIViewController, 
                sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning?

返回一个动画对象(UIViewControllerAnimatedTransitioning)来负责具体的动画效果,具体步骤如下:

  • ①. 首先调用 transitioning delegate 的 interactionControllerForPresentation: 判断交互动画是否可用
  • ②. 调用 transitionDuration: 得到动画持续时间
  • ③. UIKit 根据第一步是否启用了 交互式动画 来执行相应的方法:
    • 非交互式动画,直接调用 transitionDuration:
    • 交互式动画, 调用 startInteractiveTransition:
  • ④. 在动画完成时,自定义 animator 要调用 completeTransition: 方法,让UIKit 知道动画结束了,然后执行 presentViewController:animated:completion: 中的 completion handler,最后执行 animator 的 animationEnded: 方法

在动画结束时,调用 completeTransition: 是必要的,因为 UIKit 只有在动画结束时才能将控制权交还给你的 App

3.The Transitioning Context Object

在动画开始前,UIKit 创建 transitioning context 对象来存储:该如何执行过渡动画,并且该动画是否是可交互的,你第 2 步的 animator 对象需要知道这些信息来执行动画(特别是自定义动画的时候)

4.The Transition Coordinator

不管是内置,还是自定义的过渡,UIKit 会为创建一个 transition coordinator 对象,该对象可以处理额外的动画,除了 presentation and dismissal 之外,当界面旋转或 VC frame 改变时也可以看做是 transitions。

所有这些 transitions 都反应在 view 的层次结构中,transition coordinator 可以追踪这些改变然后执行动画。

一般『受影响』的 VC 会有个 transitionCoordinator 属性,通过该属性得到这个 transition coordinator

注意 A transition coordinator 的生命周期仅存在于过渡动画的发生过程中,他属于类 UIViewControllerTransitionCoordinator 且遵循 UIViewControllerTransitionCoordinatorContext 协议

二、Presenting a View Controller Using Custom Animations

使用自定义动画 present 一个 view controller,需要做三件事情:

  1. 创建一个需要展示的 controller
  2. 创建自定义的 transitioning delegate 对象,然后赋给 VC 的 transitioningDelegate 属性,该 delegate 对象会负责提供相应的动画对象
  3. 调用 presentViewController:animated:completion: 方法来 present VC

当执行到第三步,UIKit 会初始化 presentation 进程,Presentations 将在下次 run loop 时迭代执行,直到 animator 执行 completeTransition: 方法。

交互式过渡允许你在过渡过程中执行触摸事件,而非交互式过渡仅在 animator 指定的时间内执行

三、Implementing the Transitioning Delegate

创建并返回一个 animator 对象

- (id<UIViewControllerAnimatedTransitioning>)
    animationControllerForPresentedController:(UIViewController *)presented
                         presentingController:(UIViewController *)presenting
                             sourceController:(UIViewController *)source {
    MyAnimator* animator = [[MyAnimator alloc] init];
    return animator;
}

四、Implementing Your Animator Objects

上面创建的 animator 对象其实是一个遵守 UIViewControllerAnimatedTransitioning 协议的对象,一个 animator 对象最核心的是 animateTransition: 方法,我们用其来创建真正的动画。整个动画过程可以分为下面三步:

1.Getting the Animation Parameters

仔细观察

func animateTransition(_ transitionContext: UIViewControllerContextTransitioning)  

该方法带一个 context transitioning 对象,我们可以通过该对象来获取动画所需要的必要数据(不要自己缓存数据)

  • 调用 viewControllerForKey: 两次来得到 "fromVC" 和 "toVC"
  • 调用 containerView 得到动画执行的容器 View
  • 调用 viewForKey: 来得到一个 view,用来添加或移除
  • 调用 finalFrameForViewController: 来得到最终状态下的 frame(CGRect)

“fromVC” and “toVC” 在 presentation 和 dismissal 状态下,刚好是相反的

2.Creating the Transition Animations

基本的操作都是一样的,从 transitioning context 里获取相应的信息,然后创建真正的动画

Presentation 动画:

  • 使用 viewControllerForKey: and viewForKey: 方法获取 VC 和 View
  • 设置 to View 的初始位置
  • 通过 finalFrameForViewController: 方法得到 to View 的结束时位置
  • 添加 “to” View 到容器 View
  • 创建动画:
    • 在你的 animation block 中,将 “to” View 动画到位于容器 View 中的 final 位置
    • 在 completion block 中调用 completeTransition: 方法,然后做一些清理工作

Dismissal 动画:

  • 使用 viewControllerForKey: and viewForKey: 方法获取 VC 和 View
  • 计算出 “from” View 的最终位置,这个 View 是最终会被 dismiss 掉的那个
  • 添加 “to” View 到容器 View 中:因为在 presentation 时,该 View 其实属于 presenting view controller,在完成 presentation 后,会被从 容器 View 中移除,所以 Dismiss 时要添加回来
  • 创建动画:
    • 在你的 animation block 中,将 “from” View 动画到位于容器 View 中的 final 位置
    • 在 completion block 里将“from” View 从容器 View 中移除,然后调用 completeTransition: 方法,最后做一些清理工作

使用默认的 Model 方式显示一个全屏页面,完成后 UIKit 会默认把 Presenting VC 的 View 给从层级中移除掉

下面是 Apple 提供的一个 Sample Code

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    // Get the set of relevant objects.
    UIView *containerView = [transitionContext containerView];
    UIViewController *fromVC = [transitionContext
            viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC   = [transitionContext
            viewControllerForKey:UITransitionContextToViewControllerKey];

    UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];

    // Set up some variables for the animation.
    CGRect containerFrame = containerView.frame;
    CGRect toViewStartFrame = [transitionContext initialFrameForViewController:toVC];
    CGRect toViewFinalFrame = [transitionContext finalFrameForViewController:toVC];
    CGRect fromViewFinalFrame = [transitionContext finalFrameForViewController:fromVC];

    // Set up the animation parameters.
    if (self.presenting) {
        // Modify the frame of the presented view so that it starts
        // offscreen at the lower-right corner of the container.
        toViewStartFrame.origin.x = containerFrame.size.width;
        toViewStartFrame.origin.y = containerFrame.size.height;
    }
    else {
        // Modify the frame of the dismissed view so it ends in
        // the lower-right corner of the container view.
        fromViewFinalFrame = CGRectMake(containerFrame.size.width,
                                      containerFrame.size.height,
                                      toView.frame.size.width,
                                      toView.frame.size.height);
    }

    // Always add the "to" view to the container.
    // And it doesn't hurt to set its start frame.
    [containerView addSubview:toView];
    toView.frame = toViewStartFrame;

    // Animate using the animator's own duration value.
    [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];

            // After a failed presentation or successful dismissal, remove the view.
            if ((self.presenting && !success) || (!self.presenting && success)) {
                [toView removeFromSuperview];
            }

            // Notify UIKit that the transition has finished
            [transitionContext completeTransition:success];
    }];
}

3.Cleaning Up After the Animations

因为 completeTransition: 方法会触发:

  • presentViewController:animated:completion: 方法中的 completion handlers
  • animator 对象自己的 animationEnded:

因此 completeTransition: 最好放在 animation block 的 completion handler 中

一旦过渡过程被取消,你应该使用 transitionWasCancelled 返回的 value

四、Adding Interactivity to Your Transitions

最容易的方式是使用 UIPercentDrivenInteractiveTransition 来做交互动画,你可以直接或定义子类。如果创建子类,通常在 init 方法或 startInteractiveTransition: 添加关于触摸事件的 GestureRecognizer 官方提供了 sample code:

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

当触摸事件发生时,根据 "垂直移动距离/ContentView" 来控制过渡执行进度。

-(void)handleSwipeUpdate:(UIGestureRecognizer *)gestureRecognizer {
    UIView* container = [self.contextData containerView];

    if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
        // Reset the translation value at the beginning of the gesture.
        [self.panGesture setTranslation:CGPointMake(0, 0) inView:container];
    }
    else if (gestureRecognizer.state == UIGestureRecognizerStateChanged) {
        // Get the current translation value.
        CGPoint translation = [self.panGesture translationInView:container];

        // Compute how far the gesture has travelled vertically,
        //  relative to the height of the container view.
        CGFloat percentage = fabs(translation.y / CGRectGetHeight(container.bounds));

        // Use the translation value to update the interactive animator.
        [self updateInteractiveTransition:percentage];
    }
    else if (gestureRecognizer.state >= UIGestureRecognizerStateEnded) {
        // Finish the transition and remove the gesture recognizer.
        [self finishInteractiveTransition];
        [[self.contextData containerView] removeGestureRecognizer:self.panGesture];
    }
}

五、Creating Animations that Run Alongside a Transition

在过渡的过程中,伴随着可以执行一些额外的动画,比如一个 presented VC 在过渡过程中,可以在自身 View 层级上执行一些动画。

操作方法:

  1. 通过 presentedVCpresentingVCtransitionCoordinator 属性获取到 transition coordinator 对象(该对象的生命周期仅存在于过渡期内)
  2. 调用 transition coordinator 对象的 animateAlongsideTransition:completion:animateAlongsideTransitionInView:animation:completion: 在过渡开始的同时执行动画

六、Using a Presentation Controller with Your Animations

对于自定义的 presentations,你可以创建自己的 presentation controller。该对象通常管理着自定义的 chrome,并且因为该对象并不管理某个特定 VC 的 View,因此你可以重用他。

presentedVC 的 transitioning delegate 对象在他自身的 delegate 方法里会返回一个 presentation controller,你所需要做到就是创建一个 presentation controller,然后放到上面的 delegate 方法中 return 就好了。别忘了设置 PresentedVC 的 modalTransitionStyle.Custom

presentation controller 对象执行动画的过程和 animator 对象执行动画的过程是并行的,在过渡结束时,presentation controller 有机会对 View hierarchy 做最终的调整。


-EOF-

如果感觉此文对你有帮助,请随意打赏支持作者 😘

chengway

认清生活真相之后依然热爱它!

Subscribe to Talk is cheap, Show me the world!

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!