美文网首页牛叉的demo有意思(★^O^★)学无止境
iOS自定义转场动画(push、pop动画)

iOS自定义转场动画(push、pop动画)

作者: 逆流丶而上 | 来源:发表于2016-09-27 18:04 被阅读4267次

    iOS7推出了新的转场动画API,以协议id<UIViewControllerInterativeTransition>、id<UIViewAnimatedTransitioning>方式开放给开发者,不同于代理、类别,这样更易于我们自定义动画,更加灵活。下面介绍一下自定义转场动画

    仿酷狗push push
    要使用的协议
    • UIViewControllerInteractiveTransitioning 交互协议,主要在右滑返回时用到
    • UIViewControllerAnimatedTransitioning 动画协议,含有动画时间及转场上下文两个必须实现协议
    • UIViewControllerContextTransitioning 动画协议里边的协议之一,动画实现的主要部分
    • UIPrecentDrivenInteractiveTransition 用在交互协议,百分比控制当前动画进度。
    自定义步骤
    • 首先要实现navigation的代理,navigation有两个返回id类型的协议,实现这两个协议

    第一个方法返回一个UIPercentDrivenInterativeTransition类型的对象即可,这个对象默认实现了UIPercentInterativeTransitioning协议,需要注意的是,这个返回值主要是用于交互动画,也就是右滑返回时需要用到,这里我在基类baseViewController定义了一个UIPercentDrivenInterativeTransition类型的属性。
    - (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController )navigationController
    interactionControllerForAnimationController:(WTKBaseAnimation
    ) animationControlle{
    return animationControlle.interactivePopTransition;
    }
    第二个方法我自定义了一个遵循UIViewControllerAnimationTransitioning协议的类WTKBaseAnimation,
    - (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
    animationControllerForOperation:(UINavigationControllerOperation)operation
    fromViewController:(WTKBaseViewController *)fromVC
    toViewController:(UIViewController *)toVC{
    if (fromVC.interactivePopTransition)
    {
    WTKBaseAnimation *animation = [[WTKBaseAnimation alloc]initWithType:operation Duration:0.6 animateType:self.animationType];
    animation.interactivePopTransition = fromVC.interactivePopTransition;
    return animation; //手势
    }
    else
    {
    WTKBaseAnimation *animation = [[WTKBaseAnimation alloc]initWithType:operation Duration:0.6 animateType:self.animationType];
    return animation;//非手势
    };}

    第二个方法返回对象自定义如下


    baseAnimation构建方法.png

    也就是需要把navigation的代理方法中的参数都传过来。
    在本类中,需要实现转场动画协议:
    - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext{
    return self.duration;}

    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
    if (self.transitionType == UINavigationControllerOperationPush)
    {
        [self push:transitionContext];
    }
    else if (self.transitionType == UINavigationControllerOperationPop)
    {
        [self pop:transitionContext];
    }}   
    

    [self push:transitionContext]; [self pop:transitionContext];在本类中并没有真正的实现,具体交给子类实现

    Paste_Image.png
    - (void)push:(id<UIViewControllerContextTransitioning>)transitionContext{}
    - (void)pop:(id<UIViewControllerContextTransitioning>)transitionContext{}
    

    下面介绍子类的具体实现,

    push

    |
    <pre><code>- (void)push:(id<UIViewControllerContextTransitioning>)transitionContext {

    UIViewController * fromVc   = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    UIViewController * toVc     = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    NSTimeInterval duration     = [self transitionDuration:transitionContext];
    
    CGRect bounds               = [[UIScreen mainScreen] bounds];
    
    fromVc.view.hidden          = YES;
    
    [[transitionContext containerView] addSubview:toVc.view];
    
    [[toVc.navigationController.view superview] insertSubview:fromVc.snapshot belowSubview:toVc.navigationController.view];
    
    toVc.navigationController.view.transform = 
    

    CGAffineTransformMakeTranslation(CGRectGetWidth(bounds), 0);

    [UIView animateWithDuration:duration
                          delay:0
         usingSpringWithDamping:1.0
          initialSpringVelocity:0
                        options:UIViewAnimationOptionCurveLinear
                     animations:^{
                         fromVc.snapshot.transform = CGAffineTransformMakeTranslation(-CGRectGetWidth(bounds) * 0.3, 0);
                         toVc.navigationController.view.transform = CGAffineTransformMakeTranslation(0, 0);
                     }
                     completion:^(BOOL finished) {
                         fromVc.view.hidden = NO;
                         [fromVc.snapshot removeFromSuperview];
                         [transitionContext completeTransition:YES];
    }];
    }
    

    </code></pre>
    其中fromVC与toVC、为函数参数transitionContext协议获得,duration调用父类方法获得,最终为navigation的代理方法中返回的时间,也可以自定义。fromVC为原来的ViewController,toVC为要push的VC
    首先将fromVC的view隐藏,使用VC的snapshot代替,snapshot为viewController的截图,这里使用类别关联属性实现的。
    [transitionContext containerView] 为容器,存转场需要的view,
    分别将toVC.view及fromVC.snapshot添加到容器中,注意添加顺序、view存放的顺序。

    下面将toVC移动到屏幕右边,这里使用的是改变transform,

    使用UIView做动画,需要注意的是,使用usingSpringWithDamping的动画,关于这个动画不再多说。
    UIView动画,需要把fromVC移动到左边(移动多少可自定),toVC移动到右边。
    动画完成后,需要把原来隐藏的fromVC.view显示,添加到容器的view移除,当前显示的vc不需要移除。
    ** 最后需要调用转场完成方法** [transitionContext completeTransition:YES];

    Pop

    |

      - (void)pop:(id<UIViewControllerContextTransitioning>)transitionContext {
    
    WTKBaseViewController * fromVc  = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    UIViewController * toVc         = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    NSTimeInterval duration         = [self transitionDuration:transitionContext];
    
    CGRect bounds                   = [[UIScreen mainScreen] bounds];
    
    [fromVc.view addSubview:fromVc.snapshot];
    fromVc.navigationController.navigationBar.hidden = YES;
    fromVc.view.transform = CGAffineTransformIdentity;
    
    toVc.view.hidden                = YES;
    toVc.snapshot.transform         = CGAffineTransformMakeTranslation(-CGRectGetWidth(bounds) * 0.3, 0);
    
    [[transitionContext containerView] addSubview:toVc.view];
    [[transitionContext containerView] addSubview:toVc.snapshot];
    [[transitionContext containerView] sendSubviewToBack:toVc.snapshot];
    
    if (fromVc.interactivePopTransition)
    {
        [UIView animateWithDuration:duration
                              delay:0
                            options:UIViewAnimationOptionCurveLinear
                         animations:^{
                             fromVc.view.transform = CGAffineTransformMakeTranslation(CGRectGetWidth(bounds), 0.0);
                             toVc.snapshot.transform = CGAffineTransformIdentity;
                         }
                         completion:^(BOOL finished) {
    
                             toVc.navigationController.navigationBar.hidden = NO;
                             toVc.view.hidden = NO;
    
                             [fromVc.snapshot removeFromSuperview];
                             [toVc.snapshot removeFromSuperview];
                             fromVc.snapshot = nil;
    
                             if (![transitionContext transitionWasCancelled]) {
                                 toVc.snapshot = nil;
                             }
    
                             [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                         }];
    
    }
    else
    {
        [UIView animateWithDuration:duration
                              delay:0
             usingSpringWithDamping:1
              initialSpringVelocity:0
                            options:UIViewAnimationOptionCurveLinear
                         animations:^{
                             fromVc.view.transform = CGAffineTransformMakeTranslation(CGRectGetWidth(bounds), 0.0);
                             toVc.snapshot.transform = CGAffineTransformIdentity;
                         }
                         completion:^(BOOL finished) {
    
                             toVc.navigationController.navigationBar.hidden = NO;
                             toVc.view.hidden = NO;
    
                             [fromVc.snapshot removeFromSuperview];
                             [toVc.snapshot removeFromSuperview];
                             fromVc.snapshot = nil;
    
                             if (![transitionContext transitionWasCancelled]) {
                                 toVc.snapshot = nil;
                             }
    
                             [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                         }];
    }}
    

    pop方法与push类似,不再多说,需要注意的是** 一定要区分手势和非手势 **,也就是如果点击按钮返回,需要使用usingSpringWithDamping动画,右滑返回不使用这个。
    判断方式fromVc.interactivePopTransition,这个为在基类baseViewController里边自定义的一个UIPercentDrivenInterativeTransition类型的属性,也就是navigation代理方法- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(WTKBaseAnimation*) animationControlle的返回值。

    • 在baseViewController中添加手势:UIPanGestureRecognizer,添加到self.view上面,viewDidLoad中如下:

    if (self.navigationController && self != self.navigationController.viewControllers.firstObject)
    {
        UIPanGestureRecognizer *popRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handlePopRecognizer:)];
        [self.view addGestureRecognizer:popRecognizer];
        popRecognizer.delegate = self;
    }
    

    在手势方法中创建UIPercentInterativeTransition,在拖动过程中,用这个实例变量调用updateInteractiveTransition方法,代码如下


    - (void)handlePopRecognizer:(UIPanGestureRecognizer *)recognizer{
    CGFloat progress = [recognizer translationInView:self.view].x / CGRectGetWidth(self.view.frame);
    progress = MIN(1.0, MAX(0.0, progress));
    NSLog(@"progress---%.2f",progress);
    if (recognizer.state == UIGestureRecognizerStateBegan)
    {
        self.interactivePopTransition = [[UIPercentDrivenInteractiveTransition alloc]init];
        [self.navigationController popViewControllerAnimated:YES];
    }
    else if (recognizer.state == UIGestureRecognizerStateChanged)
    {
        [self.interactivePopTransition updateInteractiveTransition:progress];
    }
    else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled)
    {
        if (progress > 0.25)
        {
            [self.interactivePopTransition finishInteractiveTransition];
        }
        else
        {
            [self.interactivePopTransition cancelInteractiveTransition];
        }
        self.interactivePopTransition = nil;
    }}
    

    上面定义的progress,为了记录滑动的百分比,随时更新interactivePopTransition 当手势结束,根据progress判断当前是否可以pop回来,这里是以0.25为标准。

    代码连接 git连接

    相关文章

      网友评论

      • 7728d3c96a71:动画小白,iOS10貌似有bug。。滑动手势中...toView和fromView都没有显示出来。
        逆流丶而上:@Victory_LAU 这个目前只发现在7和7p上面snapshotView这个方法,已向苹果举报这个了。
        7728d3c96a71:@逆流丶而上 7/7p 上面是空白是系统问题,还是方法上有问题?可以解决吗?
        逆流丶而上:@Victory_LAU 在7上面是空白的,截图呢个方法无效。
      • Tatinic:Hi,有没有发现一个bug.如果在 viewWillAppear 里面设置了 navigationItem. 然后使用手势交互跳转的时候,跳转到一半放弃,UINavigationController 对 navigationItem 的管理就会混乱了.
        逆流丶而上:这样肯定会有问题的啊,两个页面公用一个navigationController
      • 5d51964693b5:可以 我一直很好奇 酷狗的那个效果怎么做的 受教了
        5d51964693b5:我已经在看了 受教了 多谢你
        逆流丶而上:@DeathOFDeath 文章后面有连接,你可以下载来看看。
        逆流丶而上:@DeathOFDeath 这几个动画我都是使用的UIView动画,可以设置旋转、位移之类的。酷狗的这个就是设置的旋转,push,把要push的view先旋转,,然后动画让他旋转回来

      本文标题:iOS自定义转场动画(push、pop动画)

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