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

二、分析
对于上面的三种类型的控制器,系统都会为它们设置一个代理,通过这个代理方法去监测它们切换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之间的切换

四、实现一个自定义的模态动画
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];
-

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

五、实现一个自定义的导航动画
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];

六、实现一个自定义的标签栏切换动画
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图

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