iOS:探究視圖控制器的轉場動畫

1、介紹ios

在iOS開發中,轉場動畫的使用無處不見,不僅是咱們本身更多的使用UIViewblock動畫實現一個轉場動畫,其實,在咱們實現VC控制器跳轉的時候都是轉場動畫的實現,例如標籤欄控制器的切換、模態動畫present和dismiss、導航控制器的push和pop。實現它們的轉場動畫,只須要實現它們的動畫協議便可,提及來有點太籠統,不如看下面的圖吧:ide

 

2、分析函數

對於上面的三種類型的控制器,系統都會爲它們設置一個代理,經過這個代理方法去監測它們切換VC的過程,這個過程僅僅是出現和消失的過程,至於這個過程是什麼過渡效果,這個代理是無論的。要想這個過程是有動畫的,那麼在這些過程當中,也就是代理函數中,須要另外再返回一個實現動畫的對象,這個對象必須遵循實現動畫的協議,在這個協議中開發者能夠重寫自定義轉場動畫。下面會慢慢演示這三種類型控制器的自定義轉場動畫。佈局

重寫不可交互轉場動畫的核心協議內容:動畫

//重寫動畫協議
@protocol UIViewControllerAnimatedTransitioning <NSObject>

//動畫執行時間
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;

//自定義動畫效果
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;

@end

重寫可交互轉場動畫的核心協議內容:atom

//重寫動畫協議
@protocol UIViewControllerInteractiveTransitioning <NSObject>

//自定義動畫效果
- (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext;

@end

系統提供的一個百分比可交互轉場動畫核心類內容:spa

//系統提供的百分比動畫類,已經遵循了可交互協議
@interface UIPercentDrivenInteractiveTransition : NSObject <UIViewControllerInteractiveTransitioning>
- (void)pauseInteractiveTransition; - (void)updateInteractiveTransition:(CGFloat)percentComplete; - (void)cancelInteractiveTransition; - (void)finishInteractiveTransition; @end

 

3、轉場動畫View之間的切換3d

 

4、實現一個自定義的模態動畫代理

一、概述code

正如咱們所知,系統爲咱們提供的模態動畫默認是從底部present出,而後dismiss回到底部。 雖說這個基本可以知足使用,可是若是咱們還想使用其餘形式的模態動畫例如從頂部present出dismiss回到頂部,這個時候就須要對系統默認的轉場動畫進行自定義了。

二、詳解

(1)要自定義模態轉場動畫,首先須要給被模態的控制器設置一個實現了UIViewControllerAnimatedTransitioning協議的代理,這些協議方法能夠監測動畫執行的過程,代理和協議以下:

//代理
@protocol UIViewControllerTransitioningDelegate;
@interface UIViewController(UIViewControllerTransitioning)
@property (nullable, nonatomic, weak) id <UIViewControllerTransitioningDelegate> transitioningDelegate API_AVAILABLE(ios(7.0));
@end
//協議
@protocol
UIViewControllerTransitioningDelegate <NSObject> @optional //present時調用,返回一個實現了不可交互轉場動畫協議的代理 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source; //dismiss時調用,返回一個實現了不可交互轉場動畫協議的代理 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed; //presnt過程當中交互時調用,返回一個實現了可交互的轉場動畫協議的代理 - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator; //dismiss過程當中交互時調用,返回一個實現了可交互的轉場動畫協議的代理 - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator; //返回新的模態彈框控制器(這個是對模態風格進行自定義時調用,後面會說到) - (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(nullable UIViewController *)presenting sourceViewController:(UIViewController *)source API_AVAILABLE(ios(8.0)); @end

(2)而後在上面的協議方法中返回一個實現了UIViewControllerAnimatedTransitioning協議的代理,在這個代理的協議方法中能夠真正重寫轉場動畫了,協議以下:

@protocol UIViewControllerAnimatedTransitioning <NSObject>
//動畫執行時間
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
//自定義轉場動畫
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
@optional

(3)自定義轉場動畫實現以下【注意:Singleton單例類和UIView+Extesion分類須要本身去拷貝引入】

  • 設置UIViewControllerAnimatedTransitioning代理對象TransitionDelegate,監測動畫執行過程,將其設置爲單例
    #import <UIKit/UIKit.h>
    #import "Singleton.h"
    @interface TransitionDelegate : NSObject<UIViewControllerTransitioningDelegate>
    SingletonH(TransitionDelegate);
    @end
    #import "TransitionDelegate.h"
    #import "CustomAnimationTransition.h"
    
    @implementation TransitionDelegate
    SingletonM(TransitionDelegate);
    
    #pragma mark - <UIViewControllerTransitioningDelegate>
    
    //展現的動畫
    - (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
    {
        CustomAnimationTransition *animation = [[CustomAnimationTransition alloc]init];
        animation.presented = YES;
        return animation;
    }
    
    //關閉的動畫
    - (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
    {
        CustomAnimationTransition *animation = [[CustomAnimationTransition alloc]init];
        animation.presented = NO;
        return animation;
    }
    @end
  • 設置UIViewControllerAnimatedTransitioning代理對象CustomTransitionAnimationTransition,重寫動畫效果
    #import <UIKit/UIKit.h>
    
    @interface CustomAnimationTransition : NSObject<UIViewControllerAnimatedTransitioning>
    //判斷是present仍是dismiss, YES:present  NO:dismisss
    @property (assign,nonatomic)BOOL presented;
    @end
  • //設置過渡動畫(modal和dismiss的動畫都須要在這裏處理)
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
    {
        // UITransitionContextToViewKey,
        // UITransitionContextFromViewKey.
        
        //出來的動畫
        if (self.presented) {
            
            //獲取並添加轉場視圖
            UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
            [transitionContext.containerView addSubview:toView];
             
            //設置動畫從上往下出來
            toView.y = -toView.height;
            
            [UIView animateWithDuration:duration animations:^{
                
                toView.y = 0;
                
            } completion:^(BOOL finished) {
                
                //移除視圖
                BOOL cancle = [transitionContext transitionWasCancelled];
                if (cancle) {
                    [toView removeFromSuperview];
                }
                
                //動畫完成後,視圖上的事件才能處理
                [transitionContext completeTransition:!cancle];
            }];
        }
        //銷燬的動畫
        else
        {
            //獲取轉場視圖
            UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
            
            [UIView animateWithDuration:duration animations:^{
                
                fromView.y = -fromView.height;
    
            } completion:^(BOOL finished) {
                
                //移除視圖
                BOOL cancle = [transitionContext transitionWasCancelled];
                if (!cancle) {
                    [fromView removeFromSuperview];
                }
                
                //動畫完成後,視圖上的事件才能處理
                [transitionContext completeTransition:!cancle];
            }];
        }
    }
  • 開始執行,結果如gif圖 
    //present
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[[SecondViewController alloc] init]];
    nav.transitioningDelegate = [TransitionDelegate sharedTransitionDelegate];//自定義轉場動畫
    nav.modalPresentationStyle = UIModalPresentationFullScreen;
    [self presentViewController:nav animated:YES completion:nil];

 (4)咱們已經實現了一個簡單的自定義模態不可交互的轉場動畫,其實,在模態控制器的時候,咱們還能夠自定義可交互的轉場動畫以及設置自定義的模態風格。可交互的轉場動畫一下子再討論,先來討論一下模態風格,系統在iOS13以前默認都是滿屏模式的UIModalPresentationFullScreen,可是iOS13以後,默認是UIModalPresentationPageSheet。系統提供的模態風格以下:

//模態風格枚舉
typedef NS_ENUM(NSInteger, UIModalPresentationStyle) {
    UIModalPresentationFullScreen = 0,
    UIModalPresentationPageSheet ,
    UIModalPresentationFormSheet ,
    UIModalPresentationCurrentContext ,
    UIModalPresentationCustom , //自定義
    UIModalPresentationOverFullScreen ,
    UIModalPresentationOverCurrentContext ),
    UIModalPresentationPopover ,
    UIModalPresentationBlurOverFullScreen ,
    UIModalPresentationNone,
    UIModalPresentationAutomatic , 
};

 (5)從上面的枚舉能夠看到,系統是支持咱們實現本身的風格的,也就是自定義。在實現自定義以前,必定得知道UIPresentationController這個類,這個是彈出框控件,模態的控制器都是由它進行管理,主要代碼以下:

//重寫此方法能夠在彈框即將顯示時執行所須要的操做
- (void)presentationTransitionWillBegin;
//重寫此方法能夠在彈框顯示完畢時執行所須要的操做 - (void)presentationTransitionDidEnd:(BOOL)completed;
//重寫此方法能夠在彈框即將消失時執行所須要的操做 - (void)dismissalTransitionWillBegin;
//重寫此方法能夠在彈框消失以後執行所須要的操做 - (void)dismissalTransitionDidEnd:(BOOL)completed;
//重寫決定了彈出框的frame - (CGRect)frameOfPresentedViewInContainerView;
//重寫對containerView進行佈局 - (void)containerViewWillLayoutSubviews; - (void)containerViewDidLayoutSubviews;
//初始化方法 - (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController;

(6)額外再提一個知識點,由於一下子在自定義模態風格時會涉及到。在本文開篇結構圖中介紹了建立的轉場動畫都是在轉場動畫上下文UIViewControllerContextTransitioning協議中完成的,那麼這個轉場動畫的執行是誰管理呢?看結構圖以下,沒錯,是由UIViewControllerTransitionCoordinator這個代理協調器在協調器上下文中完成的,系統給UIViewController提供了一個分類,這個分類持有這個代理協調器,經過這個代理協調器能夠拿到執行轉場動畫的方法。最終,咱們能夠本身添加一些操做與轉場動畫同步執行。

 

UIViewControllerContextTransitioning協議核心內容

@protocol UIViewControllerTransitionCoordinatorContext <NSObject>
// 執行的屬性
@property(nonatomic, readonly, getter=isAnimated) BOOL animated;
@property(nonatomic, readonly) UIModalPresentationStyle presentationStyle;
@property(nonatomic, readonly) NSTimeInterval transitionDuration;
@property(nonatomic, readonly) UIView *containerView;
@property(nonatomic, readonly) CGAffineTransform targetTransform 

// 參與控制器
// UITransitionContextToViewControllerKey、UITransitionContextFromViewControllerKey
- (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key;

// 參與的視圖
// UITransitionContextToViewKey、UITransitionContextFromViewKey
- (nullable __kindof UIView *)viewForKey:(UITransitionContextViewKey)key API_AVAILABLE(ios(8.0));
@end

UIViewControllerTransitionCoordinator協議核心內容

// 與動畫控制器中的轉場動畫同步,執行其餘動畫
- (BOOL)animateAlongsideTransition:(void (^ __nullable)(id <UIViewControllerTransitionCoordinatorContext>context))animation
                        completion:(void (^ __nullable)(id <UIViewControllerTransitionCoordinatorContext>context))completion;
 
// 與動畫控制器中的轉場動畫同步,在指定的視圖內執行動畫
- (BOOL)animateAlongsideTransitionInView:(nullable UIView *)view
                               animation:(void (^ __nullable)(id <UIViewControllerTransitionCoordinatorContext>context))animation
                              completion:(void (^ __nullable)(id <UIViewControllerTransitionCoordinatorContext>context))completion;

UIViewController(UIViewControllerTransitionCoordinator) 分類核心內容

//持有轉場動畫執行協調器
@interface UIViewController(UIViewControllerTransitionCoordinator)
@property(nonatomic, readonly, nullable) id <UIViewControllerTransitionCoordinator> transitionCoordinator;
@end

(7)自定義模態風格實現以下【注意:Singleton單例類和UIView+Extesion分類須要本身去拷貝引入】

  • 設置UIViewControllerAnimatedTransitioning代理對象TransitionDelegate,監測動畫執行過程並返回模態風格,將其設置爲單例
    #import <UIKit/UIKit.h>
    #import "Singleton.h"
    
    @interface TransitionDelegate : NSObject<UIViewControllerTransitioningDelegate>
    SingletonH(TransitionDelegate);
    @end
    #import "TransitionDelegate.h"
    #import "CustomPresentationController.h"
    #import "CustomAnimationTransition.h"
    
    @implementation TransitionDelegate
    SingletonM(TransitionDelegate);
    
    #pragma mark - <UIViewControllerTransitioningDelegate>
    //返回模態風格
    -(UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source
    {
        return [[CustomPresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting];
    }
    
    //展現的動畫
    - (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
    {
        CustomAnimationTransition *animation = [[CustomAnimationTransition alloc]init];
        animation.presented = YES;
        return animation;
    }
    
    //關閉的動畫
    - (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
    {
        CustomAnimationTransition *animation = [[CustomAnimationTransition alloc]init];
        animation.presented = NO;
        return animation;
    }
    @end
  • 設置UIViewControllerAnimatedTransitioning代理對象CustomTransitionAnimationTransition,重寫動畫效果
    #import <UIKit/UIKit.h>
    
    @interface CustomAnimationTransition : NSObject<UIViewControllerAnimatedTransitioning>
    //判斷是present仍是dismiss, YES:present  NO:dismisss
    @property (assign,nonatomic)BOOL presented;
    @end
    #import "CustomAnimationTransition.h"
    #import "UIView+Extension.h"
    
    const CGFloat duration = 0.5f;
    
    @implementation CustomAnimationTransition
    
    #pragma mark -<UIViewControllerAnimatedTransitioning>
    //動畫時間
    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
    {
        return duration;
    }
    
    //設置過渡動畫(modal和dismiss的動畫都須要在這裏處理)
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
    {
        // UITransitionContextToViewKey,
        // UITransitionContextFromViewKey.    
        
       //返現此處並無添加toView到containerView中以及從containerView中移除toView,與上面的有區別。
    //我把添加和移除toView的操做放到了下面的自定義的模態風格類中完成的

    //
    出來的動畫 if (self.presented) { UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey]; //設置動畫從上往下出來 toView.y = -toView.height; [UIView animateWithDuration:duration animations:^{ toView.y = 0; } completion:^(BOOL finished) { //動畫完成後,視圖上的事件才能處理 [transitionContext completeTransition:YES]; }]; } //銷燬的動畫 else { [UIView animateWithDuration:duration animations:^{ UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; fromView.y = -fromView.height; } completion:^(BOOL finished) { //動畫完成後,視圖上的事件才能處理 [transitionContext completeTransition:YES]; }]; } }
  • 設置自定義的模態風格類CustomPresenttationController
    #import "CustomPresentationController.h"
    
    @implementation CustomPresentationController
    
    //能夠改變被模態的控制器視圖的尺寸
    - (CGRect)frameOfPresentedViewInContainerView
    {
       //CGRectInset: 在containerView的frame基礎上,將width減少100,將height減少200
    //containerView是容納presentedView的一個容器 return CGRectInset(self.containerView.bounds, 50, 100); } //將上面重置的frame完成佈局 - (void)containerViewDidLayoutSubviews { self.presentedView.frame = self.frameOfPresentedViewInContainerView; [super containerViewDidLayoutSubviews]; } //過渡即將展現時的處理 //這個過程能夠改變視圖屬性、或者添加視圖等 - (void)presentationTransitionWillBegin { self.presentedView.frame = self.containerView.frame; [self.containerView addSubview:self.presentedView]; } //過渡展現完成 //作清理工做 - (void)presentationTransitionDidEnd:(BOOL)completed { if (!completed) { [self.presentedView removeFromSuperview]; } } //過渡即將消失時的處理 //這個過程能夠改變視圖屬性等 - (void)dismissalTransitionWillBegin { //例如改變透明度,與轉場控制器中的轉場動畫同步執行 [self.presentingViewController.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) { self.presentedView.alpha = 0.f; } completion:nil]; } //過渡消失完成 //作清理工做 - (void)dismissalTransitionDidEnd:(BOOL)completed { if (completed) { [self.presentedView removeFromSuperview]; } } @end
  • 開始執行,結果如gif圖
    //present
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[[SecondViewController alloc] init]];
    nav.transitioningDelegate = [TransitionDelegate sharedTransitionDelegate];//自定義轉場動畫
    nav.modalPresentationStyle = UIModalPresentationCustom; //自定義模態風格
    [self presentViewController:nav animated:YES completion:nil];

 (8)自定義模態轉場動畫和自定義模態風格咱們都實現完了,可是上面的動畫過程當中都是不可交互的,那麼要想實現可交互的動畫該怎麼作呢?如上面所說的,在dismiss時返回一個實現了UIViewControllerInteractiveTransitioning協議的代理或者直接是原生類UIPercentDrivenInteractiveTransition對象。其中,UIPercentDrivenInteractiveTransition是系統封裝好了百分比驅動,用起來很簡單,那麼真正的實現原理仍是咱們去實現一下。下面我們來實現導航模式的交互效果,以下:

  • TransitioningDelegate
    #import <UIKit/UIKit.h>
    #import "Singleton.h"

    @interface TransitioningDelegate : NSObject<UIViewControllerTransitioningDelegate> SingletonH(TransitioningDelegate); @end
    #import "TransitioningDelegate.h"
    #import "CustomAnimationTransition.h"
    #import "CustomInteractiveTransition.h"
    
    @implementation TransitioningDelegate
    SingletonM(TransitioningDelegate);
    
    #pragma mark - UIViewControllerTransitioningDelegate
    
    //present
    - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
        
         //這裏仍是採用自定義的轉場動畫方式進行present,使其present時從屏幕右側滑入
    CustomAnimationTransition
    *animation = [[CustomAnimationTransition alloc]init]; animation.presented = YES; return animation; } //dismiss,必須重寫 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed { //這裏採用自定義的轉場動畫覆蓋系統的dismiss效果,在dismiss時,因爲自定義了交互動畫,因此係統本身的dismiss動畫不會執行
    CustomAnimationTransition
    *animation = [[CustomAnimationTransition alloc] init]; animation.presented = NO; return animation; } //將要dismiss時的交互行爲,必須重寫 - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator { CustomInteractiveTransition *animation = [[CustomInteractiveTransition alloc] init]; return animation; } @end
  • CustomAnimationTransition
    #import <UIKit/UIKit.h>
    
    @interface CustomAnimationTransition : NSObject<UIViewControllerAnimatedTransitioning>
    //判斷是present仍是dismiss, YES:present  NO:dismisss
    @property (assign,nonatomic)BOOL presented;
    @end
    #import "CustomAnimationTransition.h"
    #import "UIView+Extension.h"
    
    const CGFloat duration = 0.5f;
    
    @implementation CustomAnimationTransition
    
    #pragma mark -<UIViewControllerAnimatedTransitioning>
    //動畫時間
    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
    {
        return duration;
    }
    
    //設置過渡動畫(modal和dismiss的動畫都須要在這裏處理)
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
    {
        // UITransitionContextToViewKey,
        // UITransitionContextFromViewKey.
        
        //出來的動畫
        if (self.presented) {
            
            //獲取並添加轉場視圖
            UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
            [transitionContext.containerView addSubview:toView];
             
            //設置動畫從右往左出來
            toView.x = toView.width;
    
            [UIView animateWithDuration:duration animations:^{
    
                toView.x = 0;
    
            } completion:^(BOOL finished) {
                
                //移除視圖
                BOOL cancle = [transitionContext transitionWasCancelled];
                if (cancle) {
                    [toView removeFromSuperview];
                }
                
                //動畫完成後,視圖上的事件才能處理
                [transitionContext completeTransition:!cancle];
            }];
        }
        //銷燬的動畫
        else
        {
            //不作處理,而是交給自定義的交互動畫去完成
        }
    }
    @end
  • CustomInteractiveTransition
    #import <UIKit/UIKit.h>
    #import "Singleton.h"
    
    @interface CustomInteractiveTransition : NSObject<UIViewControllerInteractiveTransitioning>
    SingletonH(CustomInteractiveTransition); //採用單例的方式主要是爲了保存交互上下文 //動畫進度更新
    -(void)updateAnimationProgress:(CGFloat)progress;
    
    //動畫完成
    -(void)finish;
    
    //動畫取消
    -(void)cancel;
    
    @end
    #import "CustomInteractiveTransition.h"
    #import "UIView+Extension.h"
    
    @interface CustomInteractiveTransition ()
    @property (nonatomic, strong) id<UIViewControllerContextTransitioning> context;
    @end
    
    @implementation CustomInteractiveTransition
    SingletonM(CustomInteractiveTransition);
    
    #pragma mark - UIViewControllerInteractiveTransitioning
    
    //開始交互時調用
    - (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
        
        //保存上下文
        self.context = transitionContext;
        
        //更改視圖層級
        UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
        UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
        [transitionContext.containerView insertSubview:toView belowSubview:fromView];
    }
    
    
    //動畫進度更新
    -(void)updateAnimationProgress:(CGFloat)progress {
        
        UIView *fromView = [self.context viewForKey:UITransitionContextFromViewKey];
        fromView.x = self.context.containerView.width * progress;
        
    }
    
    //動畫完成
    -(void)finish {
        
    UIView *fromView = [self.context viewForKey:UITransitionContextFromViewKey]; [UIView animateWithDuration:
    0.2 animations:^{ fromView.x += self.context.containerView.width; } completion:^(BOOL finished) {
    [fromView removeFromSuperView];
    [self.context completeTransition:finished]; }]; }
    //動畫取消 -(void)cancel {
    UIView *fromView = [self.context viewForKey:UITransitionContextFromViewKey]; [UIView animateWithDuration:
    0.2 animations:^{ fromView.x = 0; } completion:^(BOOL finished) { [fromView removeFromSuperView];
    [self.context cancelInteractiveTransition]; }]; }
    @end
  • 在被模態的控制器添加拖拽手勢
    #import "SecondViewController.h"
    #import "CustomInteractiveTransition.h"
    
    @interface SecondViewController ()
    
    @end
    
    @implementation SecondViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.title = @"secondVc";
        self.view.backgroundColor = [UIColor redColor];
        
        [self.view addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]];
    }
    
    -(void)pan:(UIPanGestureRecognizer *)pan {
        
        CGPoint translatedPoint = [pan translationInView:self.view];
        CGFloat progress = translatedPoint.x / [UIScreen mainScreen].bounds.size.width;
        if (progress < 0) {
            return;
        }
        //拖拽的距離進度比
        progress = fabs(progress);
        CustomInteractiveTransition *transition = [[CustomInteractiveTransition alloc] init];
        switch (pan.state) {
            case UIGestureRecognizerStateBegan:
                [self dismissViewControllerAnimated:YES completion:nil];
                break;
            case UIGestureRecognizerStateChanged:
                [transition updateAnimationProgress:progress];
                break;
            case UIGestureRecognizerStateEnded:
            {
                if (progress > 0.5) {
                    [transition finish];
                }else{
                    [transition cancel];
                }
                break;
            }
            default:
                break;
        }
    }
    @end
  • 開始執行,結果如gif圖
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[[SecondViewController alloc] init]];
    nav.transitioningDelegate = [TransitioningDelegate sharedTransitioningDelegate];//自定義可交互轉場動畫
    nav.modalPresentationStyle = UIModalPresentationFullScreen; //系統模態風格
    [self presentViewController:nav animated:YES completion:nil];  

 

5、實現一個自定義的導航動畫

一、重寫導航控制器的協議,返回自定義的導航轉場動畫,動畫實現的方式和modal思想一致,就不截圖實現了,重寫的核心協議以下:

//重寫導航控制器協議
@protocol UINavigationControllerDelegate <NSObject>
@optional
................

//返回一個實現了自定義交互動畫的對象
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                          interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController;


//返回一個實現了普通動畫的對象
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC;
@end

二、 如今就來自定義一個導航轉場動畫,步驟以下:

  • 建立一個CustomNavigationTransition類,實現導航控制器的協議
    #import <UIKit/UIKit.h>
    #import "Singleton.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface CustomNavigationTransition : NSObject<UINavigationControllerDelegate>
    SingletonH(CustomNavigationTransition);
    @end
    
    NS_ASSUME_NONNULL_END
    #import "CustomNavigationTransition.h"
    #import "CustomNavigationAnimation.h"
    
    @implementation CustomNavigationTransition
    SingletonM(CustomNavigationTransition);
    
    #pragma mark - UINavigationControllerDelegate
    
    //返回一個實現了普通動畫的對象
    - (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                       animationControllerForOperation:(UINavigationControllerOperation)operation
                                                    fromViewController:(UIViewController *)fromVC
                                                               toViewController:(UIViewController *)toVC {
        
        CustomNavigationAnimation *animation = [[CustomNavigationAnimation alloc] init];
        animation.operation = operation;
        return animation;
    }
    
    @end
  • 自定義一個CustomNavigationAnimation類,實現動畫協議,重寫動畫效果
    #import <UIKit/UIKit.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface CustomNavigationAnimation : NSObject<UIViewControllerAnimatedTransitioning>
    @property (nonatomic, assign) UINavigationControllerOperation operation;
    @end
    
    NS_ASSUME_NONNULL_END
    #import "CustomNavigationAnimation.h"
    #import "UIView+Extension.h"
    
    const CGFloat _duration = 0.5f;
    
    @implementation CustomNavigationAnimation
    
    #pragma mark -<UIViewControllerAnimatedTransitioning>
    //動畫時間
    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
    {
        return _duration;
    }
    
    //設置過渡動畫(modal和dismiss的動畫都須要在這裏處理)
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
    {
        // UITransitionContextToViewKey,
        // UITransitionContextFromViewKey.
        
        //push
        if (self.operation == UINavigationControllerOperationPush) {
            
            //轉場視圖
            UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
            [transitionContext.containerView addSubview:toView];
             
            //設置動畫從右上push進來
            toView.x = toView.width;
            toView.y = -toView.height;
            
            [UIView animateWithDuration:_duration animations:^{
    
                toView.x = 0;
                toView.y = 0;
                
            } completion:^(BOOL finished) {
                
                //移除視圖
                BOOL cancle = [transitionContext transitionWasCancelled];
                if (cancle) {
                    [toView removeFromSuperview];
                }
                
                //動畫完成後,視圖上的事件才能處理
                [transitionContext completeTransition:!cancle];
            }];
        }
        //pop
        else if(self.operation == UINavigationControllerOperationPop)
        {
            //轉場視圖,更改層級關係
            UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
            UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
            [transitionContext.containerView insertSubview:toView belowSubview:fromView];
    
            [UIView animateWithDuration:_duration animations:^{
                
                //pop返回右上
                fromView.x = fromView.width;
                fromView.y = -fromView.height;
                
            } completion:^(BOOL finished) {
                
                //移除視圖
                BOOL cancle = [transitionContext transitionWasCancelled];
                if (!cancle) {
                    [fromView removeFromSuperview];
                }
                
                //動畫完成後,視圖上的事件才能處理
                [transitionContext completeTransition:!cancle];
            }];
        }
    }
    @end
  • 開始執行,結果如gif圖
    //push
    ThirdViewController *vc = [[ThirdViewController alloc] init];
    self.navigationController.delegate = [CustomNavigationTransition sharedCustomNavigationTransition];
    [self.navigationController pushViewController:vc animated:YES];


6、實現一個自定義的標籤欄切換動畫

一、重寫標籤欄控制器的協議,返回自定義的標籤欄切換轉場動畫,動畫實現的方式和modal思想一致,就不截圖實現了,重寫的核心協議以下:

//重寫標籤欄協議
@protocol UITabBarControllerDelegate <NSObject>
@optional
......................

//返回一個實現了可交互的標籤欄轉場動畫對象
- (nullable id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController
                      interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController;

//返回一個實現了普通的標籤欄轉場動畫對象
- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
            animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                              toViewController:(UIViewController *)toVC;

@end

二、 如今就來自定義一個標籤轉場動畫,步驟以下:

  • 建立一個CustomTabBarViewController類,設置代理CustomTabbarTransition實例
    //注意:我使用StoryBoard搭建的界面
    #import <UIKit/UIKit.h>
    NS_ASSUME_NONNULL_BEGIN
    @interface CustomTabBarViewController : UITabBarController
    @end
    NS_ASSUME_NONNULL_END
    #import "CustomTabBarViewController.h"
    #import "CustomTabbarTransition.h"
    
    @interface CustomTabBarViewController ()
    
    @end
    
    @implementation CustomTabBarViewController
    
    -(instancetype)initWithCoder:(NSCoder *)coder {
        if (self = [super initWithCoder:coder]) {
            //設置代理
            self.delegate = [CustomTabbarTransition sharedCustomTabbarTransition];
        }
        return self;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
    }
    @end
  • 建立一個CustomTabbarTransition類,實現標籤欄控制器的協議
    #import <UIKit/UIKit.h>
    #import "Singleton.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface CustomTabbarTransition : NSObject<UITabBarControllerDelegate>
    SingletonH(CustomTabbarTransition);
    @end
    
    NS_ASSUME_NONNULL_END
    #import "CustomTabbarTransition.h"
    #import "CustomTabbarAnimation.h"
    
    @implementation CustomTabbarTransition
    SingletonM(CustomTabbarTransition);
    
    #pragma mark - UITabBarControllerDelegate
    //返回一個實現了普通動畫的對象
    - (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
    animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                                           toViewController:(UIViewController *)toVC {
        
        CustomTabbarAnimation *animation = [[CustomTabbarAnimation alloc] init];
        return animation;
    }
    
    @end
  • 建立一CustomTabbarAnimation類,自定義標籤切換動畫
    #import <UIKit/UIKit.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface CustomTabbarAnimation : NSObject<UIViewControllerAnimatedTransitioning>
    
    @end
    
    NS_ASSUME_NONNULL_END
    #import "CustomTabbarAnimation.h"
    #import "UIView+Extension.h"
    
    const CGFloat _Duration = 0.5f;
    
    @implementation CustomTabbarAnimation
    
    #pragma mark -<UIViewControllerAnimatedTransitioning>
    //動畫時間
    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
    {
        return _Duration;
    }
    
    //設置過渡動畫(modal和dismiss的動畫都須要在這裏處理)
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
    {
        // UITransitionContextToViewKey,
        // UITransitionContextFromViewKey.
        
        //轉場視圖
        UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
        UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
        [transitionContext.containerView addSubview:toView];
        [transitionContext.containerView sendSubviewToBack:toView];
        
        //尺寸變化,從原尺寸縮小到點
        CGRect finalFrame = CGRectInset(transitionContext.containerView.frame, transitionContext.containerView.frame.size.width/2, transitionContext.containerView.frame.size.height/2);
        
        [UIView animateWithDuration:_Duration animations:^{
                   
                    fromView.frame = finalFrame;
            
               } completion:^(BOOL finished) {
                   
                   //移除視圖
                   BOOL cancle = [transitionContext transitionWasCancelled];
                   if (!cancle) {
                       [fromView removeFromSuperview];
                   }
                   
                   //動畫完成後,視圖上的事件才能處理
                   [transitionContext completeTransition:!cancle];
               }];
    }
    @end
  • 開始執行,結果如gif圖

 

 

7、總結

好了,這三種經常使用的轉場動畫都進行了自定義,固然至於後兩種的交互轉場動畫跟modal實現原理同樣,就不介紹了。借用和修改別人的一個圖,作個總結吧,以下:

相關文章
相關標籤/搜索