iOS:探究视图控制器的转场动画
一、介绍
在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 = ; } 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 = ,
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 = ; } 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, , );
} //将上面重置的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 = .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 = ; } 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 = ; } 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 < ) {
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 = ;
toView.y = ; } 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/, transitionContext.containerView.frame.size.height/); [UIView animateWithDuration:_Duration animations:^{ fromView.frame = finalFrame; } completion:^(BOOL finished) { //移除视图
BOOL cancle = [transitionContext transitionWasCancelled];
if (!cancle) {
[fromView removeFromSuperview];
} //动画完成后,视图上的事件才能处理
[transitionContext completeTransition:!cancle];
}];
}
@end - 开始执行,结果如gif图
七、总结
好了,这三种常用的转场动画都进行了自定义,当然至于后两种的交互转场动画跟modal实现原理一样,就不介绍了。借用和修改别人的一个图,做个总结吧,如下:
iOS:探究视图控制器的转场动画的更多相关文章
- ios 导航视图控制器 跳转
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoa ...
- iOS UI-(多)视图控制器的生命周期、加载方法和模态视图方法以及屌丝方法
#import "ViewController.h" #import "SecondViewController.h" @interface ViewContr ...
- iOS 在视图控制器里面判断 应用程序的前台 后台切换 UIViewController
1.时机 用户点击home 键 应用退到后台 再次点击进入前台 在UIViewController里面 控制器如何获取相关的事件? 2.需求 (1)NSTimer 在应用程序进入后台 10秒 ...
- 玩转iOS开发 - 视图控制器生命周期
视图控制器生命周期
- 【IOS开发—视图控制器】
一.UIViewController 视图控制器是UIViewController类或者其子类对象,每个视图控制器都负责管理一个视图层次结构.在UIViewController中有一个重要的UIVie ...
- iOS 动画学习之视图控制器转场动画
一.概述 1.系统会创建一个转场相关的上下文对象,传递到动画执行器的animateTransition:和transitionDuration:方法,同样,也会传递到交互Controller的star ...
- VCTransitionsLibrary –自定义iOS交互式转场动画的库
简介 VCTransitionsLibrary 提供了许多适用于入栈,出栈,模态等场景下控制器切换时的转场动画.它本身提供了一个定义好的转场动画库,你可以拖到自己工程中直接使用;也提供了许多拥有不同转 ...
- iOS 视图控制器转场详解
iOS 视图控制器转场详解 前言的前言 唐巧前辈在微信公众号「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各项指标有了大幅度的增长,多谢唐巧前辈的推荐.有些人问我相关的问题 ...
- 笔记-iOS 视图控制器转场详解(上)
这是一篇长文,详细讲解了视图控制器转场的方方面面,配有详细的示意图和代码,为了使得文章在微信公众号中易于阅读,seedante 辛苦将大量长篇代码用截图的方式呈现,另外作者也在 Github 上附上了 ...
随机推荐
- Laravel .env 多环境配置文件
项目开发中,通常会有本地开发环境.内网测试环境.线上真实环境.这三种环境的配置通常都不尽相同,Laravel 可以通过环境变量 APP_ENV 的值来加载不同的 .env 配置文件.下面会介绍两种方 ...
- 基于Tcp穿越的Windows远程桌面(远程桌面管理工具)
基于Tcp穿越的Windows远程桌面(远程桌面管理工具) 1.<C# WinForm 跨线程访问控件(实用简洁写法)> 2.<基于.NET环境,C#语言 实现 ...
- 设计模式 - 动态代理原理及模仿JDK Proxy 写一个属于自己的动态代理
本篇文章代码内容较多,讲的可能会有些粗糙,大家可以选择性阅读. 本篇文章的目的是简单的分析动态代理的原理及模仿JDK Proxy手写一个动态代理以及对几种代理做一个总结. 对于代理模式的介绍和讲解,网 ...
- java中的char
System.out.println("char二进制位数:" + Character.SIZE);//16 即2个字节 在c语言中,char类型占一个字节,而汉子占两个字节,所以 ...
- mac下的环境变量
a. /etc/profile b. /etc/paths c. ~/.bash_profile d. ~/.bash_login e. ~/.profile f. ~/.bashrc 其中a和b是系 ...
- Docker 安装Oracle
1.使用docker 命令搜索oracle 镜像,前提是已安装了Docker docker search oracle 2.下载相应版本的oracle 镜像 docker pull sath89/o ...
- Focus on the Good 专注于好的方面
[1] Dealing with people is like digging for gold. When you go digging for an ounce of gold, you hav ...
- 三种常见字符编码:ASCII、Unicode和UTF-8
什么是字符编码? 计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是255( ...
- Codeforces 986B - Petr and Permutations
Description\text{Description}Description Given an array a[], swap random 2 number of them for 3n or ...
- [POJ2262] Goldbach’s Conjecture
Goldbach's Conjecture Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 48161 Accepted: ...