美文网首页
iOS-自定义转场动画

iOS-自定义转场动画

作者: Imkata | 来源:发表于2019-11-13 15:46 被阅读0次

    iOS中推出控制器的方式有两种:push和present,iOS的push动画基本上已经成为苹果的一个标志,最好不要自定义,不然和系统的动画不一样会显得不和谐。
    关于present,更多的可参考:present和dismiss

    下面介绍如何自定义present方式的转场动画。

    1. UIViewControllerTransitioningDelegate协议

    想自定义转场动画的VC必须遵守UIViewControllerTransitioningDelegate协议,实现协议的如下方法:

    - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
        return [EOCPresentAnimator new];
    }
    
    - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
        return [EOCDismissAnimator new];
    }
    
    - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator {
        return  interactiveTransition;
    }
    

    解释:

    1. 方法1是present的界面添加动画,返回的动画对象要遵守UIViewControllerAnimatedTransitioning协议。
    2. 方法2是为dismiss的界面添加动画,返回的动画对象要遵守UIViewControllerAnimatedTransitioning协议。
    3. 方法3是控制转场进度的类,返回的对象要遵守UIViewControllerInteractiveTransitioning协议。
      系统的类UIPercentDrivenInteractiveTransition已经遵守了这个协议,我们直接使用它的子类。

    2. 自定义动画类

    接下来我们自定义动画类,遵守UIViewControllerAnimatedTransitioning协议,实现协议的两个方法,如下:

    PresentAnimator.m文件:

    #import "EOCPresentAnimator.h"
    
    @implementation EOCPresentAnimator
    
    //时间
    - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
        return 2.f;
    }
    
    //动作 系统会自己调用
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
        
        //上下文对象包含了全部信息
        //获取容器View
        UIView *containerView = transitionContext.containerView;
        //获取到toView:也就是说从ViewCtrlA跳转到ViewCtrlB,toView是ViewCtrlB.view
        UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
        [containerView addSubview:toView];
        
        //rect范围的偏移量,大于0在右半边,下边
        CGRect frame = CGRectOffset(toView.frame, 0.f, [UIScreen mainScreen].bounds.size.height);
        toView.frame = frame;
        
        [UIView animateWithDuration:2.f animations:^{
            toView.frame = CGRectOffset(toView.frame, 0.f, -[UIScreen mainScreen].bounds.size.height);
        } completion:^(BOOL finished) {
            //结束上下文
            [transitionContext completeTransition:YES];
        }];
    }
    @end
    

    DismissAnimator.m文件:

    #import "EOCDismissAnimator.h"
    
    @implementation EOCDismissAnimator
    
    - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
        return 2.f;
    }
    
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
        
        UIView *containerView = transitionContext.containerView;
        
        //获取到toView:从ViewCtrlB dismiss 到ViewCtrlA   fromView是B, toView是A
        UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
        UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
        
        CGRect finalFrame = CGRectOffset(fromView.frame, 0.f, [UIScreen mainScreen].bounds.size.height);
        
        //containerView里面有fromView了
        //把toView放到最下面
        [containerView insertSubview:toView atIndex:0];
        
        [UIView animateWithDuration:2.f animations:^{
            fromView.frame = finalFrame;
        } completion:^(BOOL finished) {
            //它肯定实现了移除fromView的操作  取消就不完成
            [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
            
            //自己移除fromView不行,如果不结束转场,transitionView还在
            //[fromView removeFromSuperview];
        }];
    }
    @end
    

    解释:

    1. - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;方法会在从一个VC跳转到另一个VC的时候,系统自动调用。
    2. 上个方法的参数transitionContext(转场上下文)是转场的中间人,里面保存了fromVC、toVC、containerView以及completeTransition:方法等信息。

    动画类创建完成之后,我们在animationControllerForPresentedController:方法和animationControllerForDismissedController:方法里面传入两个动画对象,如下:

    #pragma mark - UIViewControllerTransitioningDelegate
    //为present的界面添加动画, 返回的动画对象要遵守UIViewControllerAnimatedTransitioning协议
    - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
        return [EOCPresentAnimator new];
    }
    
    //为dismiss的界面添加动画, 返回的动画对象要遵守UIViewControllerAnimatedTransitioning协议
    - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
        return [EOCDismissAnimator new];
    }
    

    调用:

    EOCNextViewController *nextViewCtrl = [[EOCNextViewController alloc] init];
    nextViewCtrl.transitioningDelegate = self;
    [self presentViewController:nextViewCtrl animated:YES completion:nil];
    
    效果图: present和dismiss.gif

    下面有个新需求,如何在灰色界面,通过下滑手势dismiss到上一个界面,这里我们就需要用到interactionControllerForDismissal:方法了。

    #pragma mark - UIViewControllerTransitioningDelegate
    //控制转场进度的类, 返回的对象要遵守UIViewControllerInteractiveTransitioning协议
    //系统的类UIPercentDrivenInteractiveTransition已经遵守了这个协议, 我们直接使用它的子类
    - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator {
        return  interactiveTransition;
    }
    

    这个方法需要返回一个遵守UIViewControllerInteractiveTransitioning协议的对象,由于系统的类UIPercentDrivenInteractiveTransition已经遵守了这个协议,我们直接使用它的子类,代码如下:

    EOCInteractiveTransition.h文件

    //  控制转场进度的类
    
    #import <UIKit/UIKit.h>
    
    @interface EOCInteractiveTransition : UIPercentDrivenInteractiveTransition
    
    - (void)transitionToViewController:(UIViewController *)toViewController;
    
    @end
    

    EOCInteractiveTransition.m文件

    #import "EOCInteractiveTransition.h"
    
    @interface EOCInteractiveTransition () {
        UIViewController *presentedViewController;
        BOOL shouldComplete; //是否拖拽了一半以上
    }
    
    @end
    
    @implementation EOCInteractiveTransition
    
    - (void)transitionToViewController:(UIViewController *)toViewController {
        
        presentedViewController = toViewController;
        UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
        [toViewController.view addGestureRecognizer:panGesture];
    }
    
    - (void)panAction:(UIPanGestureRecognizer *)gesture {
        
        switch (gesture.state) {
            case UIGestureRecognizerStateBegan:
                
                [presentedViewController dismissViewControllerAnimated:YES completion:nil];
                
                break;
            case UIGestureRecognizerStateChanged: {
                
                //监听当前滑动的距离
                CGPoint transitionPoint = [gesture translationInView:presentedViewController.view];
                NSLog(@"transitionPoint %@", NSStringFromCGPoint(transitionPoint));
                
                CGFloat ratio = transitionPoint.y/[UIScreen mainScreen].bounds.size.height;
                NSLog(@"ratio: %f", ratio);
                
                if (ratio >= 0.5) {
                    shouldComplete = YES;
                } else {
                    shouldComplete = NO;
                }
                [self updateInteractiveTransition:ratio];
            }
                break;
            case UIGestureRecognizerStateEnded:
            case UIGestureRecognizerStateCancelled: {
                if (shouldComplete) {
                    [self finishInteractiveTransition];
                } else {
                    [self cancelInteractiveTransition];
                }
            }
                break;
            default:
                break;
        }
    }
    @end
    

    调用:

    EOCNextViewController *nextViewCtrl = [[EOCNextViewController alloc] init];
    [interactiveTransition transitionToViewController:nextViewCtrl];
    nextViewCtrl.transitioningDelegate = self;
    [self presentViewController:nextViewCtrl animated:YES completion:nil];
    
    效果图: 下滑dismiss.gif

    注意点:

    1. 没present的时候,层级结构如下
    最开始.png
    1. present之后,层级结构如下
    present之后.png

    可以发现:
    ① present之后多了一个UITransitionView,这个View是专门做转场的。
    ② present之后并没有把控制器或者view直接盖上去,而是先移除旧的再添加新的,这个和push不一样。

    自定义转场动画以及自定义容器动画Demo:自定义转场动画

    相关文章

      网友评论

          本文标题:iOS-自定义转场动画

          本文链接:https://www.haomeiwen.com/subject/btzqictx.html