iOS自定義轉場動畫

更新,更簡單的自定義轉場集成!

幾句代碼快速集成自定義轉場效果+ 全手勢驅動

寫在前面

這兩天閑下來好好的研究了一下自定義轉場,關于這方面的文章網絡上已經很多了,作為新手,我想通過這篇文章把自己這幾天的相關學習心得記錄一下,方便加深印響和以后的回顧,這是我第一寫技術文章,不好之處請諒解,通過這幾天的學習,我嘗試實現了四個效果,廢話不多說,先上效果圖:

DEMO ONE:一個彈性的present動畫,支持手勢present和dismiss

彈性pop

DEMO TWO:一個類似于KeyNote的神奇移動效果push動畫,支持手勢pop

神奇移動

DEMO THREE:一個翻頁push效果,支持手勢PUSH和POP

翻頁效果

DEMO FOUR:一個小圓點擴散present效果,支持手勢dimiss

擴散效果

動手前

大家都知道從iOS7開始,蘋果就提供了自定義轉場的API,模態推送present和dismiss、導航控制器push和pop、標簽控制器的控制器切換都可以自定義轉場了,關于過多的理論我就不太多說明了,大家可以先參照onevcat大神的這篇博客:WWDC 2013 Session筆記 - iOS7中的ViewController切換,我想把整個自定義轉場的步驟做個總結:

  1. 我們需要自定義一個遵循的<UIViewControllerAnimatedTransitioning>協議的動畫過渡管理對象,并實現兩個必須實現的方法:

     //返回動畫事件  
     - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
     //所有的過渡動畫事務都在這個方法里面完成
     - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
    
  2. 我們還需要自定義一個繼承于UIPercentDrivenInteractiveTransition的手勢過渡管理對象,我把它成為百分比手勢過渡管理對象,因為動畫的過程是通過百分比控制的

  3. 成為相應的代理,實現相應的代理方法,返回我們前兩步自定義的對象就OK了 !

    模態推送需要實現如下4個代理方法,iOS8新的那個方法我暫時還沒有發現它的用處,所以暫不討論

     //返回一個管理prenent動畫過渡的對象
     - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
     //返回一個管理pop動畫過渡的對象
     - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;
     //返回一個管理prenent手勢過渡的對象
     - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator;
     //返回一個管理pop動畫過渡的對象
     - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator;  
    

    導航控制器實現如下2個代理方法

     //返回轉場動畫過渡管理對象
     - (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                       interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS(7_0);
     //返回手勢過渡管理對象
     - (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                animationControllerForOperation:(UINavigationControllerOperation)operation
                                             fromViewController:(UIViewController *)fromVC
                                               toViewController:(UIViewController *)toVC  NS_AVAILABLE_IOS(7_0);  
    

    標簽控制器也有相應的兩個方法

     //返回轉場動畫過渡管理對象
     - (nullable id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController
                   interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController NS_AVAILABLE_IOS(7_0);
     //返回手勢過渡管理對象
     - (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
         animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                           toViewController:(UIViewController *)toVC  NS_AVAILABLE_IOS(7_0);  
    
  4. 如果看著這些常常的代理方法名頭疼的話,沒關系,先在demo中用起來吧,慢慢就習慣了,其實哪種自定義轉場都只需要這3個步驟,如果不需要手勢控制,步驟2還可以取消,現在就讓我們動手來實現效果吧

動手吧!

demo one

1、我們首先創建2個控制器,為了方便我稱做present操作的為vc1、被present的為vc2,點擊一個控制器上的按鈕可以push出另一個控制器
2、 然后我們創建一個過渡動畫管理的類,遵循<UIViewControllerAnimatedTransitioning>協議,我這里是XWPresentOneTransition,由于我們要同時管理present和dismiss2個動畫,你可以實現相應的兩個類分別管理兩個動畫,但是我覺得用一個類來管理就好了,看著比較舒服,邏輯也比較緊密,因為present和dismiss的動畫邏輯很類似,寫在一起,可以相互參考,所以我定義了一個枚舉和兩個初始化方法:

    XWPresentOneTransition.h

    typedef NS_ENUM(NSUInteger, XWPresentOneTransitionType) {
        XWPresentOneTransitionTypePresent = 0,//管理present動畫
        XWPresentOneTransitionTypeDismiss//管理dismiss動畫
    };

    @interface XWPresentOneTransition : NSObject<UIViewControllerAnimatedTransitioning>
    //根據定義的枚舉初始化的兩個方法
    + (instancetype)transitionWithTransitionType:(XWPresentOneTransitionType)type;
    - (instancetype)initWithTransitionType:(XWPresentOneTransitionType)type;   

3、 然后再.m文件里面實現必須實現的兩個代理方法

    @implementation XWPresentOneTransition
    - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
        return 0.5;
    }

    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
        //為了將兩種動畫的邏輯分開,變得更加清晰,我們分開書寫邏輯,
        switch (_type) {
            case XWPresentOneTransitionTypePresent:
                [self presentAnimation:transitionContext];
                break;
        
            case XWPresentOneTransitionTypeDismiss:
                [self dismissAnimation:transitionContext];
                break;
        }
    }
    //實現present動畫邏輯代碼
    - (void)presentAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
    }
    //實現dismiss動畫邏輯代碼
    - (void)dismissAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{

    }  

4、 設置vc2的transitioningDelegate,我就設為它自己咯,我實在vc2的init方法中設置的,并實現代理方法

    - (instancetype)init
    {
        self = [super init];
        if (self) {
            self.transitioningDelegate = self;
            //為什么要設置為Custom,在最后說明.
            self.modalPresentationStyle = UIModalPresentationCustom;
        }
        return self;
    }
    
    - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
        //這里我們初始化presentType
        return [XWPresentOneTransition transitionWithTransitionType:XWPresentOneTransitionTypePresent];
    }
    - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{
        //這里我們初始化dismissType
        return [XWPresentOneTransition transitionWithTransitionType:XWPresentOneTransitionTypeDismiss];
    }  

5、 至此我們所有的準備工作就做好了,下面只需要專心在presentAnimation:方法和dismissAnimation方法中實現動畫邏輯就OK了,先看presentAnimation:

        - (void)presentAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{`
        //通過viewControllerForKey取出轉場前后的兩個控制器,這里toVC就是vc1、fromVC就是vc2
        UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        //snapshotViewAfterScreenUpdates可以對某個視圖截圖,我們采用對這個截圖做動畫代替直接對vc1做動畫,因為在手勢過渡中直接使用vc1動畫會和手勢有沖突, 如果不需要實現手勢的話,就可以不是用截圖視圖了,大家可以自行嘗試一下
        UIView *tempView = [fromVC.view snapshotViewAfterScreenUpdates:NO];
        tempView.frame = fromVC.view.frame;
        //因為對截圖做動畫,vc1就可以隱藏了
        fromVC.view.hidden = YES;
        //這里有個重要的概念containerView,如果要對視圖做轉場動畫,視圖就必須要加入containerView中才能進行,可以理解containerView管理著所有做轉場動畫的視圖
        UIView *containerView = [transitionContext containerView];
        //將截圖視圖和vc2的view都加入ContainerView中
        [containerView addSubview:tempView];
        [containerView addSubview:toVC.view];
        //設置vc2的frame,因為這里vc2present出來不是全屏,且初始的時候在底部,如果不設置frame的話默認就是整個屏幕咯,這里containerView的frame就是整個屏幕
        toVC.view.frame = CGRectMake(0, containerView.height, containerView.width, 400);
        //開始動畫吧,使用產生彈簧效果的動畫API
        [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.55 initialSpringVelocity:1.0 / 0.55 options:0 animations:^{
            //首先我們讓vc2向上移動
            toVC.view.transform = CGAffineTransformMakeTranslation(0, -400);
            //然后讓截圖視圖縮小一點即可
            tempView.transform = CGAffineTransformMakeScale(0.85, 0.85);
        } completion:^(BOOL finished) {
            //使用如下代碼標記整個轉場過程是否正常完成[transitionContext transitionWasCancelled]代表手勢是否取消了,如果取消了就傳NO表示轉場失敗,反之亦然,如果不用手勢present的話直接傳YES也是可以的,但是無論如何我們都必須標記轉場的狀態,系統才知道處理轉場后的操作,否者認為你一直還在轉場中,會出現無法交互的情況,切記!
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
            //轉場失敗后的處理
            if ([transitionContext transitionWasCancelled]) {
                //失敗后,我們要把vc1顯示出來
                fromVC.view.hidden = NO;
                //然后移除截圖視圖,因為下次觸發present會重新截圖
                [tempView removeFromSuperview];
            }
         }];
        } 

再看dismissAnimation 方法

        - (void)dismissAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
        //注意在dismiss的時候fromVC就是vc2了,toVC才是VC1了,注意這個關系
        UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        //參照present動畫的邏輯,present成功后,containerView的最后一個子視圖就是截圖視圖,我們將其取出準備動畫
        UIView *tempView = [transitionContext containerView].subviews[0];
        //動畫吧
        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
            //因為present的時候都是使用的transform,這里的動畫只需要將transform恢復就可以了
            fromVC.view.transform = CGAffineTransformIdentity;
            tempView.transform = CGAffineTransformIdentity;
        } completion:^(BOOL finished) {
            if ([transitionContext transitionWasCancelled]) {
                //失敗了標記失敗
                [transitionContext completeTransition:NO];
            }else{
                //如果成功了,我們需要標記成功,同時讓vc1顯示出來,然后移除截圖視圖,
                [transitionContext completeTransition:YES];
                toVC.view.hidden = NO;
                [tempView removeFromSuperview];
            }
            }];
        } 

6、如果不需要手勢控制,這個轉場就算完成了,下面我們來添加手勢,首先創建一個手勢過渡管理的類,我這里是XWInteractiveTransition,因為無論哪一種轉場,手勢控制的實質都是一樣的,我干脆就把這個手勢過渡管理的類封裝了一下,具體可以在.h文件里面查看,在接下來的三個轉場效果中我們都可以便捷的是使用它 .m文件說明

        //通過這個方法給控制器的View添加相應的手勢
        - (void)addPanGestureForViewController:(UIViewController *)viewController{
        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
        //將傳入的控制器保存,因為要利用它觸發轉場操作
        self.vc = viewController;
        [viewController.view addGestureRecognizer:pan];    
        }  
        
        //關鍵的手勢過渡的過程
        - (void)handleGesture:(UIPanGestureRecognizer *)panGesture{
        //persent是根據panGesture的移動距離獲取的,這里就不說明了,可具體去代碼中查看
        switch (panGesture.state) {
        case UIGestureRecognizerStateBegan:
            //手勢開始的時候標記手勢狀態,并開始相應的事件,它的作用在使用這個類的時候說明
            self.interation = YES;
            //手勢開始是觸發對應的轉場操作,方法代碼在后面
            [self startGesture];
            break;
        case UIGestureRecognizerStateChanged:{
            //手勢過程中,通過updateInteractiveTransition設置轉場過程進行的百分比,然后系統會根據百分比自動布局控件,不用我們控制了
            [self updateInteractiveTransition:persent];
            break;
        }
        case UIGestureRecognizerStateEnded:{
            //手勢完成后結束標記并且判斷移動距離是否過半,過則finishInteractiveTransition完成轉場操作,否者取消轉場操作,轉場失敗
            self.interation = NO;
            if (persent > 0.5) {
                [self finishInteractiveTransition];
            }else{
                [self cancelInteractiveTransition];
            }
            break;
        }
        default:
            break;
         }
        }  
        //觸發對應轉場操作的代碼如下,根據type(type是我自定義的枚舉值)我們去判斷是觸發哪種操作,對于push和present由于要傳入需要push和present的控制器,為了解耦,我用block把這個操作交個控制器去做了,讓這個手勢過渡管理者可以充分被復用
        - (void)startGesture{
        switch (_type) {
        case XWInteractiveTransitionTypePresent:{
            if (_presentConifg) {
                _presentConifg();
            }
        }
            break;
            
        case XWInteractiveTransitionTypeDismiss:
            [_vc dismissViewControllerAnimated:YES completion:nil];
            break;
        case XWInteractiveTransitionTypePush:{
            if (_pushConifg) {
                _pushConifg();
            }
        }
            break;
        case XWInteractiveTransitionTypePop:
            [_vc.navigationController popViewControllerAnimated:YES];
            break;
          }
        }  

7、 手勢過渡管理者就算完畢了,這個手勢管理者可以用到其他任何的模態和導航控制器轉場中,以后都不用在寫了,現在把他用起來,在vc2和vc1中創建相應的手勢過渡管理者,并放到相應的代理方法去返回它

        //創建dismiss手勢過渡管理者,present的手勢過渡要在vc1中創建,因為present的手勢是加載vc1的view上的,我選擇通過代理吧vc1中創建的手勢過渡管理者傳過來
        self.interactiveDismiss = [XWInteractiveTransition interactiveTransitionWithTransitionType:XWInteractiveTransitionTypeDismiss           GestureDirection:XWInteractiveTransitionGestureDirectionDown];
            [self.interactiveDismiss addPanGestureForViewController:self];
         [_interactivePush addPanGestureForViewController:self.navigationController];
            //返回dissmiss的手勢過渡管理
        - (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:      (id<UIViewControllerAnimatedTransitioning>)animator{
            //在沒有用手勢觸發的dismiss的時候需要傳nil,否者無法點擊dimiss,所以interation就是用來判斷是否是手勢觸發轉場的
            return _interactiveDismiss.interation ? _interactiveDismiss : nil;
        }
        
        //返回present的手勢管理,這個手勢管理者是在vc1中創建的,我用代理傳過來的
        - (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:       (id<UIViewControllerAnimatedTransitioning>)animator{
            XWInteractiveTransition *interactivePresent = [_delegate interactiveTransitionForPresent];
            return interactivePresent.interation ? interactivePresent : nil;
        }  

8、 終于完成了,再來看一下效果,是不是還不錯!

彈性pop

DEMO TWO

1、 創建動畫過渡管理者的代碼就不重復說明了,我仿造demo1,利用枚舉創建了一個同時管理push和pop的管理者,然后動畫的邏輯代碼集中在doPushAnimationdoPopAnimation中,很多內容都在demo1中說明了,下面的注釋就比較簡單了,來看看

        //Push動畫邏輯
        - (void)doPushAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
        XWMagicMoveController *fromVC = (XWMagicMoveController *)[transitionContext     viewControllerForKey:UITransitionContextFromViewControllerKey];
        XWMagicMovePushController *toVC = (XWMagicMovePushController *)[transitionContext   viewControllerForKey:UITransitionContextToViewControllerKey];
        //拿到當前點擊的cell的imageView
        XWMagicMoveCell *cell = (XWMagicMoveCell *)[fromVC.collectionView cellForItemAtIndexPath:fromVC.currentIndexPath];
        UIView *containerView = [transitionContext containerView];
        //snapshotViewAfterScreenUpdates 對cell的imageView截圖保存成另一個視圖用于過渡,并將視圖轉換到當前控制器的坐標
        UIView *tempView = [cell.imageView snapshotViewAfterScreenUpdates:NO];
        tempView.frame = [cell.imageView convertRect:cell.imageView.bounds toView: containerView];
        //設置動畫前的各個控件的狀態
        cell.imageView.hidden = YES;
        toVC.view.alpha = 0;
        toVC.imageView.hidden = YES;
        //tempView 添加到containerView中,要保證在最前方,所以后添加
        [containerView addSubview:toVC.view];
        [containerView addSubview:tempView];
        //開始做動畫
        [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.55   initialSpringVelocity:1 / 0.55 options:0 animations:^{
            tempView.frame = [toVC.imageView convertRect:toVC.imageView.bounds toView:containerView];
            toVC.view.alpha = 1;
        } completion:^(BOOL finished) {
            //tempView先隱藏不銷毀,pop的時候還會用
            tempView.hidden = YES;
            toVC.imageView.hidden = NO;
            //如果動畫過渡取消了就標記不完成,否則才完成,這里可以直接寫YES,如果有手勢過渡才需要判斷,必須標記,否則系統不會中動畫完成的部署,會出現無法交互之類的bug
            [transitionContext completeTransition:YES];
            }];
        }  

    //Pop動畫邏輯
        - (void)doPopAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
        XWMagicMovePushController *fromVC = (XWMagicMovePushController *)[transitionContext     viewControllerForKey:UITransitionContextFromViewControllerKey];
        XWMagicMoveController *toVC = (XWMagicMoveController *)[transitionContext   viewControllerForKey:UITransitionContextToViewControllerKey];
        XWMagicMoveCell *cell = (XWMagicMoveCell *)[toVC.collectionView cellForItemAtIndexPath:toVC.currentIndexPath];
        UIView *containerView = [transitionContext containerView];
        //這里的lastView就是push時候初始化的那個tempView
        UIView *tempView = containerView.subviews.lastObject;
        //設置初始狀態
        cell.imageView.hidden = YES;
        fromVC.imageView.hidden = YES;
        tempView.hidden = NO;
        [containerView insertSubview:toVC.view atIndex:0];
        [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.55   initialSpringVelocity:1 / 0.55 options:0 animations:^{
            tempView.frame = [cell.imageView convertRect:cell.imageView.bounds toView:containerView];
            fromVC.view.alpha = 0;
        } completion:^(BOOL finished) {
            //由于加入了手勢必須判斷
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
            if ([transitionContext transitionWasCancelled]) {//手勢取消了,原來隱藏的imageView要顯示出來
                //失敗了隱藏tempView,顯示fromVC.imageView
                tempView.hidden = YES;
                fromVC.imageView.hidden = NO;
            }else{//手勢成功,cell的imageView也要顯示出來
                //成功了移除tempView,下一次pop的時候又要創建,然后顯示cell的imageView
                cell.imageView.hidden = NO;
                [tempView removeFromSuperview];
            }
          }];
        }   

2、 然后將這個動畫過渡管理者和demo1中創建的手勢過渡管理者分別放到正確的代理方法中,用起來就可以了

神奇移動

DEMO THREE

1、 直接看看doPushAnimationdoPopAnimation的動畫邏輯,這次使用了CAGradientLayer給動畫的過程增加了陰影

    //Push動畫邏輯
    - (void)doPushAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    //還是使用截圖大法來完成動畫,不然還是會有奇妙的bug;
    UIView *tempView = [fromVC.view snapshotViewAfterScreenUpdates:NO];
    tempView.frame = fromVC.view.frame;
    UIView *containerView = [transitionContext containerView];
    //將將要動畫的視圖加入containerView
    [containerView addSubview:toVC.view];
    [containerView addSubview:tempView];
    fromVC.view.hidden = YES;
    [containerView insertSubview:toVC.view atIndex:0];
    //設置AnchorPoint,并增加3D透視效果
    [tempView setAnchorPointTo:CGPointMake(0, 0.5)];
    CATransform3D transfrom3d = CATransform3DIdentity;
    transfrom3d.m34 = -0.002;
    containerView.layer.sublayerTransform = transfrom3d;
    //增加陰影
    CAGradientLayer *fromGradient = [CAGradientLayer layer];
    fromGradient.frame = fromVC.view.bounds;
    fromGradient.colors = @[(id)[UIColor blackColor].CGColor,
                        (id)[UIColor blackColor].CGColor];
    fromGradient.startPoint = CGPointMake(0.0, 0.5);
    fromGradient.endPoint = CGPointMake(0.8, 0.5);
    UIView *fromShadow = [[UIView alloc]initWithFrame:fromVC.view.bounds];
    fromShadow.backgroundColor = [UIColor clearColor];
    [fromShadow.layer insertSublayer:fromGradient atIndex:1];
    fromShadow.alpha = 0.0;
    [tempView addSubview:fromShadow];
    CAGradientLayer *toGradient = [CAGradientLayer layer];
    toGradient.frame = fromVC.view.bounds;
    toGradient.colors = @[(id)[UIColor blackColor].CGColor,
                            (id)[UIColor blackColor].CGColor];
    toGradient.startPoint = CGPointMake(0.0, 0.5);
    toGradient.endPoint = CGPointMake(0.8, 0.5);
    UIView *toShadow = [[UIView alloc]initWithFrame:fromVC.view.bounds];
    toShadow.backgroundColor = [UIColor clearColor];
    [toShadow.layer insertSublayer:toGradient atIndex:1];
    toShadow.alpha = 1.0;
    [toVC.view addSubview:toShadow];
    //動畫吧
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        //翻轉截圖視圖
        tempView.layer.transform = CATransform3DMakeRotation(-M_PI_2, 0, 1, 0);
        //給陰影效果動畫
        fromShadow.alpha = 1.0;
        toShadow.alpha = 0.0;
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        if ([transitionContext transitionWasCancelled]) {
            //失敗后記得移除截圖,下次push又會創建
            [tempView removeFromSuperview];
            fromVC.view.hidden = NO;
        }
    }];
}}
    //Pop動畫邏輯
    - (void)doPopAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIView *containerView = [transitionContext containerView];
    //拿到push時候的的截圖視圖
    UIView *tempView = containerView.subviews.lastObject;
    [containerView addSubview:toVC.view];
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        //把截圖視圖翻轉回來
        tempView.layer.transform = CATransform3DIdentity;
        fromVC.view.subviews.lastObject.alpha = 1.0;
        tempView.subviews.lastObject.alpha = 0.0;
    } completion:^(BOOL finished) {
        if ([transitionContext transitionWasCancelled]) {
            [transitionContext completeTransition:NO];
        }else{
            [transitionContext completeTransition:YES];
            [tempView removeFromSuperview];
            toVC.view.hidden = NO;
        }
    }];}

2、 最后用上去在加上手勢就是這個樣子啦

翻頁效果

DEMO FOUR

1、 直接看看doPresentAnimationdoDismissAnimation的動畫邏輯,這次使用了CASharpLayer和UIBezierPath

    //Present動畫邏輯
    - (void)presentAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    //拿到控制器獲取button的frame來設置動畫的開始結束的路徑
    UINavigationController *fromVC = (UINavigationController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    XWCircleSpreadController *temp = fromVC.viewControllers.lastObject;
    UIView *containerView = [transitionContext containerView];
    [containerView addSubview:toVC.view];
    //畫兩個圓路徑
    UIBezierPath *startCycle =  [UIBezierPath bezierPathWithOvalInRect:temp.buttonFrame];
    //通過如下方法計算獲取在x和y方向按鈕距離邊緣的最大值,然后利用勾股定理即可算出最大半徑
    CGFloat x = MAX(temp.buttonFrame.origin.x, containerView.frame.size.width - temp.buttonFrame.origin.x);
    CGFloat y = MAX(temp.buttonFrame.origin.y, containerView.frame.size.height - temp.buttonFrame.origin.y);
    //勾股定理計算半徑
    CGFloat radius = sqrtf(pow(x, 2) + pow(y, 2));
    //以按鈕中心為圓心,按鈕中心到屏幕邊緣的最大距離為半徑,得到轉場后的path
    UIBezierPath *endCycle = [UIBezierPath bezierPathWithArcCenter:containerView.center radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    //創建CAShapeLayer進行遮蓋
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    //設置layer的path保證動畫后layer不會回彈
    maskLayer.path = endCycle.CGPath;
    //將maskLayer作為toVC.View的遮蓋
    toVC.view.layer.mask = maskLayer;
    //創建路徑動畫
    CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
    maskLayerAnimation.delegate = self;
    //動畫是加到layer上的,所以必須為CGPath,再將CGPath橋接為OC對象
    maskLayerAnimation.fromValue = (__bridge id)(startCycle.CGPath);
    maskLayerAnimation.toValue = (__bridge id)((endCycle.CGPath));
    maskLayerAnimation.duration = [self transitionDuration:transitionContext];
    maskLayerAnimation.delegate = self;
    //設置淡入淡出
    maskLayerAnimation.timingFunction = [CAMediaTimingFunction  functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [maskLayerAnimation setValue:transitionContext forKey:@"transitionContext"];
    [maskLayer addAnimation:maskLayerAnimation forKey:@"path"];
}

    //Dismiss動畫邏輯
    - (void)dismissAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UINavigationController *toVC = (UINavigationController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    XWCircleSpreadController *temp = toVC.viewControllers.lastObject;
    UIView *containerView = [transitionContext containerView];
    //畫兩個圓路徑
    CGFloat radius = sqrtf(containerView.frame.size.height * containerView.frame.size.height + containerView.frame.size.width * containerView.frame.size.width) / 2;
    UIBezierPath *startCycle = [UIBezierPath bezierPathWithArcCenter:containerView.center radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    UIBezierPath *endCycle =  [UIBezierPath bezierPathWithOvalInRect:temp.buttonFrame];
    //創建CAShapeLayer進行遮蓋
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.fillColor = [UIColor greenColor].CGColor;
    maskLayer.path = endCycle.CGPath;
    fromVC.view.layer.mask = maskLayer;
    //創建路徑動畫
    CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
    maskLayerAnimation.delegate = self;
    maskLayerAnimation.fromValue = (__bridge id)(startCycle.CGPath);
    maskLayerAnimation.toValue = (__bridge id)((endCycle.CGPath));
    maskLayerAnimation.duration = [self transitionDuration:transitionContext];
    maskLayerAnimation.delegate = self;
    maskLayerAnimation.timingFunction = [CAMediaTimingFunction  functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [maskLayerAnimation setValue:transitionContext forKey:@"transitionContext"];
    [maskLayer addAnimation:maskLayerAnimation forKey:@"path"];
}

2、最后在animationDidStop的代理方法中處理到動畫的完成邏輯,處理方式都類似

    - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    switch (_type) {
        case XWCircleSpreadTransitionTypePresent:{
            id<UIViewControllerContextTransitioning> transitionContext = [anim valueForKey:@"transitionContext"];
            [transitionContext completeTransition:YES];
            [transitionContext viewControllerForKey:UITransitionContextToViewKey].view.layer.mask = nil;
        }
            break;
        case XWCircleSpreadTransitionTypeDismiss:{
            id<UIViewControllerContextTransitioning> transitionContext = [anim valueForKey:@"transitionContext"];
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
            if ([transitionContext transitionWasCancelled]) {
                [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view.layer.mask = nil;
            }
        }
            break;
    }
}

3、 最后用上去在加上手勢就是這個樣子啦

擴散效果

總結

1、關于:self.modalPresentationStyle = UIModalPresentationCustom;我查看了視圖層級后發現,如果使用了Custom,在present動畫完成的時候,presentingView也就是demo one中的vc1的view會從containerView中移除,只是移除,并未銷毀,此時還被持有著(dismiss后還得回來呢!),如果設置custom,那么present完成后,它一直都在containerView中,只是在最后面,所以需不需要設置custom可以看動畫完成后的情況,是否還需要看見presentingViewController,但是記住如果沒有設置custom,在disMiss的動畫邏輯中,要把它加回containerView中,不然就不在咯~!
2、感覺寫了好多東西,其實只要弄懂了轉場的邏輯,其實就只需要寫動畫的邏輯就行了,其他東西都是固定的,而且蘋果提供的這種控制轉場的方式可充分解耦,除了寫的手勢過渡管理可以拿到任何地方使用,所有的動畫過渡管理者都可以很輕松的復用到其他轉場中,都不用分是何種轉場,demo沒有寫標簽控制器的轉場,實現方法也是完全類似的,大家可以嘗試一下,四個demo的github地址:自定義轉場動畫demo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 路漫漫其修遠兮,吾將上下而求索 前記 想研究自定義轉場動畫很久了,時間就像海綿,擠一擠還是有的,花了差不多有10天...
    半笑半醉間閱讀 7,519評論 10 51
  • 通過這幾天的學習,我嘗試實現了四個效果,廢話不多說,先上效果圖: DEMO ONE:一個神奇移動效果push動畫,...
    petry閱讀 1,748評論 0 5
  • 簡書上的所有內容都可以在我的個人博客上找到 這兩天學習了一下自定義轉場動畫的內容,剛開始看的時候被這幾個又長又很相...
    yahtzee_閱讀 1,331評論 1 13
  • 烏鴉有一頭不算黑的短發 穿著粉紅色的袈裟 她喜歡說話 張嘴吐出一口花 她教我們畫過幾幅畫 拿筆的時候像個啞巴 烏鴉...
    曉曉博士閱讀 475評論 0 0
  • 畢贛導演電影《路邊野餐》上映第一周獲得333萬的票房,比另一部文藝片《冬》多很多,比“驚天一跪”的《百鳥朝鳳》少不...
    顧影自言閱讀 1,024評論 0 4