天天看點

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

一、介紹

在iOS開發中,轉場動畫的使用無處不見,不隻是我們自己更多的使用UIViewblock動畫實作一個轉場動畫,其實,在我們實作VC控制器跳轉的時候都是轉場動畫的實作,例如标簽欄控制器的切換、模态動畫present和dismiss、導航控制器的push和pop。實作它們的轉場動畫,隻需要實作它們的動畫協定即可,說起來有點太籠統,不如看下面的圖吧:

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

二、分析

對于上面的三種類型的控制器,系統都會為它們設定一個代理,通過這個代理方法去監測它們切換VC的過程,這個過程僅僅是出現和消失的過程,至于這個過程是什麼過渡效果,這個代理是不管的。要想這個過程是有動畫的,那麼在這些過程中,也就是代理函數中,需要另外再傳回一個實作動畫的對象,這個對象必須遵循實作動畫的協定,在這個協定中開發者可以重寫自定義轉場動畫。下面會慢慢示範這三種類型控制器的自定義轉場動畫。

重寫不可互動轉場動畫的核心協定内容:

//重寫動畫協定@protocol UIViewControllerAnimatedTransitioning//動畫執行時間- (NSTimeInterval)transitionDuration:(nullable id )transitionContext;//自定義動畫效果- (void)animateTransition:(id )transitionContext;@end      

重寫可互動轉場動畫的核心協定内容:

//重寫動畫協定@protocol UIViewControllerInteractiveTransitioning//自定義動畫效果- (void)startInteractiveTransition:(id )transitionContext;@end      

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

//系統提供的百分比動畫類,已經遵循了可互動協定@interface UIPercentDrivenInteractiveTransition : NSObject      

三、轉場動畫View之間的切換

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

四、實作一個自定義的模态動畫

1、概述

正如我們所知,系統為我們提供的模态動畫預設是從底部present出,然後dismiss回到底部。 雖然說這個基本能夠滿足使用,但是如果我們還想使用其他形式的模态動畫例如從頂部present出dismiss回到頂部,這個時候就需要對系統預設的轉場動畫進行自定義了。

2、詳解

(1)要自定義模态轉場動畫,首先需要給被模态的控制器設定一個實作了UIViewControllerAnimatedTransitioning協定的代理,這些協定方法可以監測動畫執行的過程,代理和協定如下:

//代理@protocol UIViewControllerTransitioningDelegate;@interface UIViewController(UIViewControllerTransitioning)
@property (nullable, nonatomic, weak) id transitioningDelegate API_AVAILABLE(ios(7.0));@end      
//協定
@protocol UIViewControllerTransitioningDelegate      

(2)然後在上面的協定方法中傳回一個實作了UIViewControllerAnimatedTransitioning協定的代理,在這個代理的協定方法中可以真正重寫轉場動畫了,協定如下:

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

(3)自定義轉場動畫實作如下【注意:Singleton單例類和UIView+Extesion分類需要自己去拷貝引入】

  • 設定UIViewControllerAnimatedTransitioning代理對象TransitionDelegate,監測動畫執行過程,将其設定為單例
    #import #import "Singleton.h"@interface TransitionDelegate : NSObjectSingletonH(TransitionDelegate);@end      
    #import "TransitionDelegate.h"#import "CustomAnimationTransition.h"@implementation TransitionDelegate
    SingletonM(TransitionDelegate);#pragma mark -//展示的動畫- (id )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
    {
        CustomAnimationTransition *animation = [[CustomAnimationTransition alloc]init];
        animation.presented = YES;    return animation;
    }//關閉的動畫- (id )animationControllerForDismissedController:(UIViewController *)dismissed
    {
        CustomAnimationTransition *animation = [[CustomAnimationTransition alloc]init];
        animation.presented = NO;    return animation;
    }@end      
  • 設定UIViewControllerAnimatedTransitioning代理對象CustomTransitionAnimationTransition,重寫動畫效果
    #import @interface CustomAnimationTransition : NSObject//判斷是present還是dismiss, YES:present  NO:dismisss@property (assign,nonatomic)BOOL presented;@end      
  • //設定過渡動畫(modal和dismiss的動畫都需要在這裡處理)- (void)animateTransition:(id )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圖 
    //presentUINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[[SecondViewController alloc] init]];
    nav.transitioningDelegate = [TransitionDelegate sharedTransitionDelegate];//自定義轉場動畫nav.modalPresentationStyle = UIModalPresentationFullScreen;
    [self presentViewController:nav animated:YES completion:nil];      
  • iOS:探究視圖控制器的轉場動畫

 (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提供了一個分類,這個分類持有這個代理協調器,通過這個代理協調器可以拿到執行轉場動畫的方法。最終,我們可以自己添加一些操作與轉場動畫同步執行。

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

UIViewControllerContextTransitioning協定核心内容

@protocol UIViewControllerTransitionCoordinatorContext// 執行的屬性@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 context))animation
                        completion:(void (^ __nullable)(id context))completion; 
// 與動畫控制器中的轉場動畫同步,在指定的視圖内執行動畫- (BOOL)animateAlongsideTransitionInView:(nullable UIView *)view
                               animation:(void (^ __nullable)(id context))animation
                              completion:(void (^ __nullable)(id context))completion;      

UIViewController(UIViewControllerTransitionCoordinator) 分類核心内容

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

(7)自定義模态風格實作如下【注意:Singleton單例類和UIView+Extesion分類需要自己去拷貝引入】

  • 設定UIViewControllerAnimatedTransitioning代理對象TransitionDelegate,監測動畫執行過程并傳回模态風格,将其設定為單例
    #import #import "Singleton.h"@interface TransitionDelegate : NSObjectSingletonH(TransitionDelegate);@end      
    #import "TransitionDelegate.h"#import "CustomPresentationController.h"#import "CustomAnimationTransition.h"@implementation TransitionDelegate
    SingletonM(TransitionDelegate);#pragma mark -//傳回模态風格-(UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source
    {    return [[CustomPresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting];
    }//展示的動畫- (id )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
    {
        CustomAnimationTransition *animation = [[CustomAnimationTransition alloc]init];
        animation.presented = YES;    return animation;
    }//關閉的動畫- (id )animationControllerForDismissedController:(UIViewController *)dismissed
    {
        CustomAnimationTransition *animation = [[CustomAnimationTransition alloc]init];
        animation.presented = NO;    return animation;
    }@end      
  • #import @interface CustomAnimationTransition : NSObject//判斷是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 -      
  • 設定自定義的模态風格類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      
  • 開始執行,結果如gif圖
    //presentUINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[[SecondViewController alloc] init]];
    nav.transitioningDelegate = [TransitionDelegate sharedTransitionDelegate];//自定義轉場動畫nav.modalPresentationStyle = UIModalPresentationCustom; //自定義模态風格[self presentViewController:nav animated:YES completion:nil];      
    iOS:探究視圖控制器的轉場動畫

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

  • TransitioningDelegate
    #import      
    #import "TransitioningDelegate.h"#import "CustomAnimationTransition.h"#import "CustomInteractiveTransition.h"@implementation TransitioningDelegate
    SingletonM(TransitioningDelegate);#pragma mark - UIViewControllerTransitioningDelegate//present- (nullable id      
  • CustomAnimationTransition
    #import @interface CustomAnimationTransition : NSObject//判斷是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 -//動畫時間- (NSTimeInterval)transitionDuration:(id )transitionContext
    {    return duration;
    }//設定過渡動畫(modal和dismiss的動畫都需要在這裡處理)- (void)animateTransition:(id )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 #import "Singleton.h"@interface CustomInteractiveTransition : NSObjectSingletonH(CustomInteractiveTransition); //采用單例的方式主要是為了儲存互動上下文//動畫進度更新-(void)updateAnimationProgress:(CGFloat)progress;//動畫完成-(void)finish;//動畫取消-(void)cancel;@end      
    #import "CustomInteractiveTransition.h"#import "UIView+Extension.h"@interface CustomInteractiveTransition ()
    @property (nonatomic, strong) id      
  • 在被模态的控制器添加拖拽手勢
    #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      
  • UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[[SecondViewController alloc] init]];
    nav.transitioningDelegate = [TransitioningDelegate sharedTransitioningDelegate];//自定義可互動轉場動畫nav.modalPresentationStyle = UIModalPresentationFullScreen; //系統模态風格[self presentViewController:nav animated:YES completion:nil];      
iOS:探究視圖控制器的轉場動畫

五、實作一個自定義的導航動畫

1、重寫導航控制器的協定,傳回自定義的導航轉場動畫,動畫實作的方式和modal思想一緻,重寫的核心協定如下:

//重寫導航控制器協定@protocol UINavigationControllerDelegate@optional
................//傳回一個實作了自定義互動動畫的對象- (nullable id )navigationController:(UINavigationController *)navigationController
                          interactionControllerForAnimationController:(id ) animationController;//傳回一個實作了普通動畫的對象- (nullable id )navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC;@end      

2、 現在就來自定義一個導航轉場動畫,步驟如下:

  • 建立一個CustomNavigationTransition類,實作導航控制器的協定
    #import #import "Singleton.h"NS_ASSUME_NONNULL_BEGIN@interface CustomNavigationTransition : NSObjectSingletonH(CustomNavigationTransition);@endNS_ASSUME_NONNULL_END      
    #import "CustomNavigationTransition.h"#import "CustomNavigationAnimation.h"@implementation CustomNavigationTransition
    SingletonM(CustomNavigationTransition);#pragma mark - UINavigationControllerDelegate//傳回一個實作了普通動畫的對象- (nullable id )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 NS_ASSUME_NONNULL_BEGIN@interface CustomNavigationAnimation : NSObject@property (nonatomic, assign) UINavigationControllerOperation operation;@endNS_ASSUME_NONNULL_END      
    #import "CustomNavigationAnimation.h"#import "UIView+Extension.h"const CGFloat _duration = 0.5f;@implementation CustomNavigationAnimation#pragma mark -//動畫時間- (NSTimeInterval)transitionDuration:(id )transitionContext
    {    return _duration;
    }//設定過渡動畫(modal和dismiss的動畫都需要在這裡處理)- (void)animateTransition:(id )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      
  • //pushThirdViewController *vc = [[ThirdViewController alloc] init];
    self.navigationController.delegate = [CustomNavigationTransition sharedCustomNavigationTransition];
    [self.navigationController pushViewController:vc animated:YES];      
    iOS:探究視圖控制器的轉場動畫

六、實作一個自定義的标簽欄切換動畫

1、重寫标簽欄控制器的協定,傳回自定義的标簽欄切換轉場動畫,動畫實作的方式和modal思想一緻,重寫的核心協定如下:

//重寫标簽欄協定@protocol UITabBarControllerDelegate@optional
......................//傳回一個實作了可互動的标簽欄轉場動畫對象- (nullable id )tabBarController:(UITabBarController *)tabBarController
                      interactionControllerForAnimationController: (id )animationController;//傳回一個實作了普通的标簽欄轉場動畫對象- (nullable id )tabBarController:(UITabBarController *)tabBarController
            animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                              toViewController:(UIViewController *)toVC;@end      

2、 現在就來自定義一個标簽轉場動畫,步驟如下:

  • 建立一個CustomTabBarViewController類,設定代理CustomTabbarTransition執行個體
    //注意:我使用StoryBoard搭建的界面#import NS_ASSUME_NONNULL_BEGIN@interface CustomTabBarViewController : UITabBarController@endNS_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 #import "Singleton.h"NS_ASSUME_NONNULL_BEGIN@interface CustomTabbarTransition : NSObjectSingletonH(CustomTabbarTransition);@endNS_ASSUME_NONNULL_END      
    #import "CustomTabbarTransition.h"#import "CustomTabbarAnimation.h"@implementation CustomTabbarTransition
    SingletonM(CustomTabbarTransition);#pragma mark - UITabBarControllerDelegate//傳回一個實作了普通動畫的對象- (nullable id )tabBarController:(UITabBarController *)tabBarController
    animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                                           toViewController:(UIViewController *)toVC {
        
        CustomTabbarAnimation *animation = [[CustomTabbarAnimation alloc] init];    return animation;
    }@end      
  • 建立一個CustomTabbarAnimation類,自定義标簽切換動畫
    #import NS_ASSUME_NONNULL_BEGIN@interface CustomTabbarAnimation : NSObject@endNS_ASSUME_NONNULL_END      
    #import "CustomTabbarAnimation.h"#import "UIView+Extension.h"const CGFloat _Duration = 0.5f;@implementation CustomTabbarAnimation#pragma mark -//動畫時間- (NSTimeInterval)transitionDuration:(id )transitionContext
    {    return _Duration;
    }//設定過渡動畫(modal和dismiss的動畫都需要在這裡處理)- (void)animateTransition:(id )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      
iOS:探究視圖控制器的轉場動畫

七、總結

繼續閱讀