iOS自定义转场动画

作者: 健健锅 | 来源:发表于2016-07-18 16:20 被阅读527次

    (http://blog.devtang.com/2016/03/13/iOS-transition-guide/)之前写过一篇core Animation的文章里面提到,核心动画里面有一个很重要的部分就是转场动画,而转场动画有包括三个部分:
    1.模态方式的转场
    2.导航栏式的转场(push pop)
    3.手势驱动转场
    4.tabber转场动画
    一. 模态方式的转场

    7月-13-2016 17-23-44.gif
     默认的模态专场方式是:页面从底部弹出来.那么要实现其他的方式应该怎么做呢
    

    首先要重写专长的代理方法
    创建一个类 继承与nsobject 并且准守代理方法UIViewControllerAnimatedTransitioning

    //.h文件的内容
    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    @interface Transtion : NSObject<UIViewControllerAnimatedTransitioning>
    @property (assign, nonatomic) BOOL reverse;//用来标记是跳转还是返回
    - (instancetype)init;//初始话方法
    @end
    
    //.m文件中的部分代码
    #import "Transtion.h"
    @interface Transtion()
    @property (assign, nonatomic) id context;
    @property (strong, nonatomic) UIView* containerView;
    @end
    @implementation Transtion
    -(instancetype)init{
        if (self = [super init]) {
            
        }
        return self;
    }
    - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
        return 5;//过渡时间代理方法
    }
    
    //.m文件中重要的重写代理方法
    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
        _context = transitionContext;
        self.containerView = [transitionContext containerView];
       
        if (!self.reverse) {
             NSLog(@"跳转");
         UIViewController* toVC = [_context viewControllerForKey:UITransitionContextToViewControllerKey];//获取将要得到的第二个界面
    
        CGSize screenSize = [UIScreen mainScreen].bounds.size;
        toVC.view.frame = CGRectMake(0, 0, screenSize.width, screenSize.height);//secondVC 给页面赋值
         
          
        [UIView animateWithDuration:2 animations:^{
    
            [self.containerView.layer setTransform:[self firstTransform:M_PI/2.0]];
    //self.containerView 是转场的过渡视图容器, 将他旋转90度
        } completion:^(BOOL finished) {
                  [self.containerView addSubview:toVC.view];//旋转90度以后 将即将出现个的控制器的view添加
            
            [UIView animateWithDuration:3 animations:^{
                [self.containerView.layer setTransform:[self firstTransform:M_PI*0]];//继续旋转(归位也可以)
            } completion:^(BOOL finished) {
                              [_context completeTransition:YES];
    //结束转场时间
            }];
               }];
        }else{
         NSLog(@"返回");//如果是返回的话就走这个界面  这应该就不用解释啦把
            UIViewController* toVC = [_context viewControllerForKey:UITransitionContextToViewControllerKey];
    
            CGSize screenSize = [UIScreen mainScreen].bounds.size;
            toVC.view.frame = CGRectMake(0, 0, screenSize.width, screenSize.height);
    
            [UIView animateWithDuration:2 animations:^{
     
                [self.containerView.layer setTransform:[self firstTransform:M_PI*1.5]];
    
            } completion:^(BOOL finished) {
                   [self.containerView addSubview:toVC.view];
                
                [UIView animateWithDuration:3 animations:^{
    
                    [self.containerView.layer setTransform:[self firstTransform:M_PI*0]];
    
                }completion:^(BOOL finished) {
                    
                     [_context completeTransition:YES];
                }];
                
            }];
    
        }
    }
    
    

    控制器中的使用

    首先遵守该协议<UIViewControllerTransitioningDelegate>
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        secondViewController * second = [[secondViewController alloc]init];
        second.transitioningDelegate =self;//建立代理链接
       
        [self presentViewController:second animated:YES completion:nil];
      //  [self.navigationController pushViewController:second animated:YES];
         
    }
    
    - (Transtion*)trastion{
        if (!_trastion) {
            _trastion = [[Transtion alloc]init];
        }
        return _trastion;
    }
    
    - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
    {
        self.trastion.reverse = YES;//返回
        return self.trastion;
    }
    
    - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
    {
        self.trastion.reverse = NO;//跳转
        return self.trastion;
    }
    
    

    好了 模态的就搞定啦

    二,导航栏(push pop)
    这个很见到其实和模态差不多

    7月-14-2016 09-24-49.gif

    Transtion.h 文件里面的东西不用该

    我们要修改的是控制器里面的

    准守协议
    @interface ViewController ()<UIViewControllerTransitioningDelegate,UINavigationControllerDelegate>
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.navigationController.delegate =self;//建立代理连接很重要哦
        self.view.backgroundColor = [UIColor greenColor];
        self.navigationItem.title = @"第一页";
        // Do any additional setup after loading the view, typically from a nib.
    }
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        secondViewController * second = [[secondViewController alloc]init];
        second.transitioningDelegate =self;
       
        //[self presentViewController:second animated:YES completion:nil];
        [self.navigationController pushViewController:second animated:YES];
         
    }
    - (Transtion*)trastion{
        if (!_trastion) {
            _trastion = [[Transtion alloc]init];
        }
        return _trastion;
    }
    //push 用到的代理方法啊
    -(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
        NSLog(@"push代理");
        if (operation == UINavigationControllerOperationPush) {
            self.trastion.reverse = NO;
            return self.trastion;
    
        }else{
            self.trastion.reverse = YES;
            return self.trastion;
    
        }
    }
    ok push 搞定
    

    补充: 我们有时后或做一些复杂的转场动画这时候会用到连个常用的工具函数

    //.m文件的全部内容
    - (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates NS_AVAILABLE_IOS(7_0);//对某个view进行截图 
    - (UIView *)resizableSnapshotViewFromRect:(CGRect)rect afterScreenUpdates:(BOOL)afterUpdates withCapInsets:(UIEdgeInsets)capInsets NS_AVAILABLE_IOS(7_0);  //对某个view的某个部位进行截图
    

    场景模拟:我们可以在点击转场以后 对第一个页面的某一个部分截图 然后添加到过渡视图容器上让着个截图在转场的整个过程中都可以看到
    三. 手势驱动
    等待更新吧 因为建锅还没来得及搞~~~~~~~~~ 等我后续
    接下来我们来搞手势驱动(手势驱动个人遇到了一些问题可困惑一会可以分享)
    首先手势驱动需要重写另一个代理方法:创建类继承与UIPercentDrivenInteractiveTransition

    //.h文件
    #import <UIKit/UIKit.h>
    
    @interface MyPercentDrivenInteractiveTransition : UIPercentDrivenInteractiveTransition
    @property (nonatomic, assign) BOOL interactionInProgress;
    - (void)writeFromoViewController:(UIViewController*)fromVC AndToViewController:(UIViewController*)toVC;//一个添加手势的方法
    @end
    
    

    接下来就是识别手势并且更具滑动的时百分比驱动页面的转动

    #import "MyPercentDrivenInteractiveTransition.h"
    @interface MyPercentDrivenInteractiveTransition(){
     BOOL  _shouldCompleteTransition;
    }
    @property(nonatomic,strong)UIViewController * FromVC;
    @property(nonatomic,strong)UIViewController * ToVC;
    @end
    @implementation MyPercentDrivenInteractiveTransition
    static  float fraction = 0;
    - (void)writeFromoViewController:(UIViewController*)fromVC AndToViewController:(UIViewController*)toVC{
        self.FromVC = fromVC;
        self.ToVC = toVC;
        [self addGestureRecognizerInFromView:toVC.view];
    }
    
    - (void)addGestureRecognizerInFromView:(UIView*)fromView{
        UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(geatureRecognizer:)];
        [fromView addGestureRecognizer:pan];
    }
    -(CGFloat)completionSpeed
    {
        return 1-self.percentComplete ;
    }
    
    - (void)geatureRecognizer:(UIPanGestureRecognizer*)GestureRecognizer{
        CGPoint translation = [GestureRecognizer translationInView:GestureRecognizer.view.superview];
        NSLog(@"==== %f",translation.x);
        switch (GestureRecognizer.state) {
            case UIGestureRecognizerStateBegan:
                self.interactionInProgress = YES;
    
                [self.ToVC.navigationController popViewControllerAnimated:YES];
                break;
            case UIGestureRecognizerStateChanged:{
                fraction =  -(translation.x / 200);//200是我自己给的 可以给平款用来计算百分比
                fraction = fmin(fmaxf(fraction, 0.0), 1.0);
                NSLog(@"fraction === %f",translation.x);
                _shouldCompleteTransition = fraction > 0.5;
                //    _shouldCompleteTransition = 1;
                [self updateInteractiveTransition:fraction];
            }
                break;
         case UIGestureRecognizerStateEnded:
            case UIGestureRecognizerStateCancelled:
            {
                /**
                 *  需要判断是结束了还是取消了
                 */
                
                self.interactionInProgress = NO;
                
                if (!_shouldCompleteTransition || GestureRecognizer.state == UIGestureRecognizerStateCancelled)
                {
                    //只要有一个不成立 那么就是取消了
                    
                    [self cancelInteractiveTransition];//取消
                }
                
                else
                {
                    [self finishInteractiveTransition];//结束
                }
            }
    
            default:
                break;
        }
    }
    @end
    
    

    控制器里面要初始化这个类

    - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
        if (operation == UINavigationControllerOperationPush) {
            self.trastionAnimstion.pushOrPop = YES;
            
            [_myPercentDrivenInteractiveTransition writeFromoViewController:fromVC AndToViewController:toVC];//push时用的不是手势是按钮 这时候给toview 添加手势
            return self.trastionAnimstion;
        }else{
            self.trastionAnimstion.pushOrPop = NO;
            return self.trastionAnimstion;
        }
    }
    
    
    //这是交互的代理实现
    -(id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
    {
      
        return _myPercentDrivenInteractiveTransition.interactionInProgress ? _myPercentDrivenInteractiveTransition : nil;
       
    }
    
    

    到离这里按说改完美结束了 但是结果总是出人意外:在实现动画是
    我第一次用了前面的实现方式

    CGSize screenSize = [UIScreen mainScreen].bounds.size;
        _toVC.view.frame = CGRectMake(0, 0, screenSize.width, screenSize.height);
        [self.ContainerView addSubview:self.toVC.view];
        self.toVC.view.alpha = 0;
        [UIView animateWithDuration:TrasitonTime/2.0 animations:^{
            [self.ContainerView.layer setTransform:[self trasfrom:M_PI_2 * directFlage]];
        } completion:^(BOOL finished) {
            self.toVC.view.alpha = 1;
            [UIView animateWithDuration:TrasitonTime/2.0 animations:^{
                [self.ContainerView.layer setTransform:[self trasfrom:0]];
            } completion:^(BOOL finished) {
                [TransitionContext completeTransition:![TransitionContext transitionWasCancelled]];
            }];
            
        }];
    //没看错这就是我前面实现
    

    没看错这就是我前面实现旋转时 用的方法 但是手势驱动时不好用
    在执行下面这一段时 translation.x 打印出来为 NAN(意思就是不是一个数字),这里我搞了好久然后看了一些成功案例 进行了模仿

    case UIGestureRecognizerStateChanged:{
                fraction =  -(translation.x / 200);
                fraction = fmin(fmaxf(fraction, 0.0), 1.0);
                NSLog(@"fraction === %f",translation.x);
                _shouldCompleteTransition = fraction > 0.5;
                //    _shouldCompleteTransition = 1;
                [self updateInteractiveTransition:fraction];
            }
                break;
    

    采用了第二种方法(但是依然有问题)

    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    self.fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
       self.toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        self.ContainerView = [transitionContext containerView];
        TrasitonTime = [self transitionDuration:transitionContext];
        TransitionContext = transitionContext;
        self.fromVC.view.frame = [UIScreen mainScreen].bounds;
        self.toVC.view.frame = [UIScreen mainScreen].bounds;
        [self.ContainerView addSubview:self.toVC.view];
        if (self.pushOrPop == YES) {
            [self pushOrPresent];
        }else{
            [self popOrDismiss];
        }
    
    }
    - (void)pushOrPresent{
      //  [_ContainerView addSubview:_toVC.view];
     //   _toVC.view.alpha = 0;
        [self animationTrasFromDirect:-1.0];
        }
    
    - (void)popOrDismiss{
        
        [self animationTrasFromDirect:1.0];
    
    }
    - (void)animationTrasFromDirect:(CGFloat)directFlage{
        //这一段如果把时间改长也会出现一系列问题
        [self.toVC.view.layer setTransform:[self trasfrom: - M_PI_2 * directFlage ]];
      // _toVC.view.layer.transform = [self yRotation:directFlage  *  -M_PI_2];
        [UIView animateKeyframesWithDuration:TrasitonTime delay:0 options:0 animations:^{
            [UIView addKeyframeWithRelativeStartTime:0 relativeDuration:TrasitonTime/2.0 animations:^{
                [self.fromVC.view.layer setTransform:[self trasfrom:M_PI_2 * directFlage]];
            }];
            [UIView addKeyframeWithRelativeStartTime:TrasitonTime/2.0 relativeDuration:TrasitonTime/2.0  animations:^{
             
                [self.toVC.view.layer setTransform:CATransform3DIdentity];
            }];
        } completion:^(BOOL finished) {
            [TransitionContext completeTransition:![TransitionContext transitionWasCancelled]];
        }];
    }
    

    这个方法呢解决了手势驱动问题 但是又有新麻烦:当设置转场时间1.0s是没问题效果如下:

    7月-18-2016 15-50-34.gif

    但是如果改成3.0s 就有问题啦(当页面旋转到一半时,切换第二个页面时,没有了过渡的动画效果) 效果如图:

    7月-18-2016 15-53-55.gif

    当然在最后也找了一个完全没问题的而写法(这个写法是我借鉴网文写的但是我感觉和我的第二种方法差不多 但是效果就是没问题)

    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    // zhe一段是完全没问题的
        UIView * containerView = [transitionContext containerView];
        
    
        UIViewController * fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        
        UIViewController * toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        
        UIView * fromView = fromViewController.view;
        
        UIView * toView = toViewController.view;
        
        [containerView addSubview:toView];
        
        
    //    CATransform3D transform = CATransform3DIdentity;
    //    
    //    transform.m34 = -0.0001;
    //    
    //     [containerView.layer setSublayerTransform:transform];
        
        
    //    CGRect  initialFrame = [transitionContext initialFrameForViewController:fromViewController];
    //    
    //    
    //    //设置视图大小
    //    toView.frame = initialFrame;
    //    fromView.frame = initialFrame;
        
        //是否是反面
        float factor = self.pushOrPop ? -1.0 : 1.0;
        
        //push操作 y轴方向顺时针旋转90度 这样第一时间是看不到toView的
        toView.layer.transform = [self yRotation:factor  *  -M_PI_2];
        
        //animate
        
        NSTimeInterval duration = [self transitionDuration:transitionContext];
        
        [UIView animateKeyframesWithDuration:duration delay:0 options:0 animations:^{
            
            
            [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
                
                //fromView-----
                
               // fromView.layer.transform = [self yRotation:factor * M_PI_2];
               [fromView.layer setTransform:[self trasfrom: factor * M_PI_2]];
            }];
            
            [UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{
                
                 [toView.layer setTransform: [self trasfrom: 0]];
                //toVIew复原 在先前顺时针的基础上 逆时针旋转90度 复原
                //toView.layer.transform = [self yRotation:0.0];
                
            }];
            
            
        } completion:^(BOOL finished) {
            
            
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
            
        }];
        
    

    效果我就不上了但是保证效果嘎嘎好

    在这我想知道 各位道友知道 问题出在哪里 (我实在是找不到了这一篇断断续续写了好几天)代码地址:https://git.oschina.net/GAOZEJIAN/transitionAnimation.git

    四 , tabber转场动画
    好吧到这里我不想写啦 我感觉用上面的思路就可以解决 如果还是不会自行百度学习

    相关文章

      网友评论

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

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