一、介绍 在iOS开发中,转场动画的使用无处不见,不只是我们自己更多的使用UIViewblock动画实现一个转场动画,其实,在我们实现VC控制器跳转的时候都是转场动画的实现,例如标签栏控制器的切换、模态动画present和dismiss、导航控制器的push和pop。实现它们的转场动画,只需要实现它们的动画协议即可,说起来有点太笼统,不如看下面的图吧: 二、分析 对于上面的三种类型的控制器,系统都会为它们设置一个代理,通过这个代理方法去监测它们切换VC的过程,这个过程仅仅是出现和消失的过程,至于这个过程是什么过渡效果,这个代理是不管的。要想这个过程是有动画的,那么在这些过程中,也就是代理函数中,需要另外再返回一个实现动画的对象,这个对象必须遵循实现动画的协议,在这个协议中开发者可以重写自定义转场动画。下面会慢慢演示这三种类型控制器的自定义转场动画。 重写不可交互转场动画的核心协议内容: 复制代码 //重写动画协议 @protocol UIViewControllerAnimatedTransitioning //动画执行时间 - (NSTimeInterval)transitionDuration:(nullable id )transitionContext; //自定义动画效果 - (void)animateTransition:(id )transitionContext; @end 复制代码 重写可交互转场动画的核心协议内容: 复制代码 //重写动画协议 @protocol UIViewControllerInteractiveTransitioning //自定义动画效果 - (void)startInteractiveTransition:(id )transitionContext; @end 复制代码 系统提供的一个百分比可交互转场动画核心类内容: 复制代码 //系统提供的百分比动画类,已经遵循了可交互协议 @interface UIPercentDrivenInteractiveTransition : NSObject - (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 transitioningDelegate API_AVAILABLE(ios(7.0)); @end 复制代码 复制代码 //协议 @protocol UIViewControllerTransitioningDelegate @optional //present时调用,返回一个实现了不可交互转场动画协议的代理 - (nullable id )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source; //dismiss时调用,返回一个实现了不可交互转场动画协议的代理 - (nullable id )animationControllerForDismissedController:(UIViewController *)dismissed; //presnt过程中交互时调用,返回一个实现了可交互的转场动画协议的代理 - (nullable id )interactionControllerForPresentation:(id )animator; //dismiss过程中交互时调用,返回一个实现了可交互的转场动画协议的代理 - (nullable id )interactionControllerForDismissal:(id )animator; //返回新的模态弹框控制器(这个是对模态风格进行自定义时调用,后面会说到) - (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(nullable UIViewController *)presenting sourceViewController:(UIViewController *)source API_AVAILABLE(ios(8.0)); @end 复制代码 (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 : NSObject SingletonH(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图 复制代码 //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 // 执行的属性 @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 : NSObject SingletonH(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 复制代码 设置UIViewControllerAnimatedTransitioning代理对象CustomTransitionAnimationTransition,重写动画效果 复制代码 #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.    //发现此处并没有添加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];