欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS:探究视图控制器的转场动画

程序员文章站 2022-12-21 13:41:20
一、介绍 在iOS开发中,转场动画的使用无处不见,不只是我们自己更多的使用UIViewblock动画实现一个转场动画,其实,在我们实现VC控制器跳转的时候都是转场动画的实现,例如标签栏控制器的切换、模态动画present和dismiss、导航控制器的push和pop。实现它们的转场动画,只需要实现它 ......

一、介绍

在ios开发中,转场动画的使用无处不见,不只是我们自己更多的使用uiviewblock动画实现一个转场动画,其实,在我们实现vc控制器跳转的时候都是转场动画的实现,例如标签栏控制器的切换、模态动画present和dismiss、导航控制器的push和pop。实现它们的转场动画,只需要实现它们的动画协议即可,说起来有点太笼统,不如看下面的图吧:

iOS:探究视图控制器的转场动画

 

二、分析

对于上面的三种类型的控制器,系统都会为它们设置一个代理,通过这个代理方法去监测它们切换vc的过程,这个过程仅仅是出现和消失的过程,至于这个过程是什么过渡效果,这个代理是不管的。要想这个过程是有动画的,那么在这些过程中,也就是代理函数中,需要另外再返回一个实现动画的对象,这个对象必须遵循实现动画的协议,在这个协议中开发者可以重写自定义转场动画。下面会慢慢演示这三种类型控制器的自定义转场动画。

重写不可交互转场动画的核心协议内容:

//重写动画协议
@protocol uiviewcontrolleranimatedtransitioning <nsobject>

//动画执行时间
- (nstimeinterval)transitionduration:(nullable id <uiviewcontrollercontexttransitioning>)transitioncontext;

//自定义动画效果
- (void)animatetransition:(id <uiviewcontrollercontexttransitioning>)transitioncontext;

@end

重写可交互转场动画的核心协议内容:

//重写动画协议
@protocol uiviewcontrollerinteractivetransitioning <nsobject>

//自定义动画效果
- (void)startinteractivetransition:(id <uiviewcontrollercontexttransitioning>)transitioncontext;

@end

系统提供的一个百分比可交互转场动画核心类内容:

//系统提供的百分比动画类,已经遵循了可交互协议
@interface uipercentdriveninteractivetransition : nsobject <uiviewcontrollerinteractivetransitioning>
- (void)pauseinteractivetransition; - (void)updateinteractivetransition:(cgfloat)percentcomplete; - (void)cancelinteractivetransition; - (void)finishinteractivetransition; @end

 

三、转场动画view之间的切换

iOS:探究视图控制器的转场动画

 

四、实现一个自定义的模态动画

1、概述

正如我们所知,系统为我们提供的模态动画默认是从底部present出,然后dismiss回到底部。 虽然说这个基本能够满足使用,但是如果我们还想使用其他形式的模态动画例如从顶部present出dismiss回到顶部,这个时候就需要对系统默认的转场动画进行自定义了。

2、详解

(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];
  • 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 <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];

    iOS:探究视图控制器的转场动画

 (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];  

iOS:探究视图控制器的转场动画

 

五、实现一个自定义的导航动画

1、重写导航控制器的协议,返回自定义的导航转场动画,动画实现的方式和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

2、 现在就来自定义一个导航转场动画,步骤如下:

  • 创建一个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];

    iOS:探究视图控制器的转场动画


六、实现一个自定义的标签栏切换动画

1、重写标签栏控制器的协议,返回自定义的标签栏切换转场动画,动画实现的方式和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

2、 现在就来自定义一个标签转场动画,步骤如下:

  • 创建一个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图

 iOS:探究视图控制器的转场动画

 

七、总结

好了,这三种常用的转场动画都进行了自定义,当然至于后两种的交互转场动画跟modal实现原理一样,就不介绍了。借用和修改别人的一个图,做个总结吧,如下:

iOS:探究视图控制器的转场动画