转场/控制器切换
含义:在
NavigationController
里push
或pop
一个View Controller
,在TabBarController
中切换到其他View Controller
,以Modal
方式显示另外一个View Controller
,这些都是控制器切换(View Controller Transition)
,简称转场。
在 iOS 7 之前
,我们只能使用系统提供的转场效果,iOS 7之后
苹果开放了相关 API 允许我们对转场效果进行全面定制,它是 以协议的方式
开放了自定义转场的 API,协议的好处是不再拘泥于具体的某个类,只要是遵守该协议的对象都能参与转场,非常灵活,这样对自定义转场动画以及交互手段的支持带来了无限可能。
转场实现的本质
转场的本质是当前视图消失和下一视图出现,基于此进行动画,因为在转场的过程中,作为容器的父控制器维护着多个子控制器,但在视图结构上,只保留一个子控制器的视图。
目前主流的自定义转场
- 在
UINavigationController
中Push 和 Pop
- 在
UITabBarController
中切换TabBar
- 模态(Modal) 转场:
Present 和 Dismiss
,仅限于modalPresentationStyle
属性为UIModalPresentationFullScreen
或UIModalPresentationCustom
这两种模式 -
UICollectionViewController
布局转场(与UINavigationController
结合的转场方式)
转场动画的实现
转场协议由5种协议组成,在实际开发中只需我们提供其中的两个或三个便能实现绝大部分的转场动画。
- 1.转场代理(Transition Delegate) - 必须使用:
自定义转场的第一步便是提供转场代理,告诉系统使用我们提供的代理而不是系统的默认代理来执行转场,不同类型的控制器遵守的协议不同,如下:
/**
* 除了是 iOS7 新增的协议,其他两种在 iOS2 里就存在了
*/
//UINavigationController 的 delegate 属性遵守该协议
//UITabBarController 的 delegate 属性遵守该协议
//UIViewController 的 transitioningDelegate 属性遵守该协议
- 2.动画控制器(Animation Controller) - 必须使用:
最重要的部分,遵守
协议,负责添加视图以及执行动画,由我们实现 - 3.交互控制器(Interaction Controller) - 可选使用:
通过交互手段,通常是手势来驱动动画控制器实现的动画,使得用户能够控制整个过程;遵守
协议 - 4.转场环境(Transition Context) - 必须使用:
提供转场中需要的数据;遵守
协议;由UIKit
在转场开始前生成
并提供给我们提交的动画控制器和交互控制器使用 - 5.转场协调器(Transition Coordinator) - 可选使用:
可在转场动画发生的同时并行执行其他的动画,主要在 Modal 转场和交互转场取消时使用,其他时候很少用到,由 UIKit 在转场时生成,遵守
协议;UIViewController 在 iOS 7 中新增了方法transitionCoordinator()
返回一个遵守该协议的对象,且该方法只在该控制器处于转场过程中才返回一个此类对象,不参与转场时返回 nil
下面就拿一个我之前写好的小栗子来看看自定义转场是如何实现的吧,这里我以特殊的Modal转场
为例讲一下,TabBarController
或是NavigationController
自定义转场实现相对简单些,但本质其实都是相同的,先看效果图吧:
Modal转场的实现
- Demo内的视图结构
首先控制器遵守转场代理
//实现转场的触发操作
- (IBAction)clickBtn {
PresentedViewController *presentedVC = [PresentedViewController new];
//系统转场
//presentedVC.modalTransitionStyle = UIModalTransitionStyleCoverVertical; //默认样式
//自定义转场 模态转场 需要代理实现
presentedVC.modalPresentationStyle = UIModalPresentationCustom;
//遵守转场代理 代理需要强引用(即:self.delegate)
presentedVC.transitioningDelegate = self.delegate;
[self presentViewController:presentedVC animated:YES completion:nil];
}
在TransitionDelegate遵守对应的协议
@interface TransitionDelegate : NSObject
实现其代理协议
/**
* 返回当view显示时执行动画的对象,该对象需实现转场动画
*/
- (id)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
//return nil;
return [ModalAnimation new];
}
/**
* 返回当view消失时执行动画的对象,该对象需实现转场动画
*/
- (id)animationControllerForDismissedController:(UIViewController *)dismissed{
//return nil;
return [ModalAnimation new];
}
创建动画控制器添加转场视图以及执行相应的动画,对于动画控制器来说,转场方式并不重要,可以对fromView 和 toView
进行任何动画,需要遵守转场动画协议
,并实现对应的协议方法,如下:
//返回转场动画时间
- (NSTimeInterval)transitionDuration:(nullable id )transitionContext{
return 0.5;
}
/**
* 最重要的方法 必须实现 否则自定义转场无效
* 该方法接受一个遵守协议的转场环境对象
* 该转场环境对象提供了转场所需要的重要数据:参与转场的视图控制器和转场过程的状态信息
* 在转场开始前生成遵守转场环境协议的对象transitionContext
* 转场环境对象transitionContext提供了以下信息
* 1. containerView 容器视图
* 2. 获取参与转场的视图控制器,有 UITransitionContextFromViewControllerKey 和 UITransitionContextToViewControllerKey 两个 Key
* 2. iOS8之后 新增的 API 用于方便获取参与转场的视图,有 UITransitionContextFromViewKey 和 UITransitionContextToViewKey 两个 Key
* 在 iOS8 中可通过方法"viewForKey"来获取参与转场的三个重要视图,在 iOS7 中则需要通过对应的视图控制器来获取
*/
- (void)animateTransition:(id )transitionContext{
//获取转场相关的两个控制器 iOS7的API
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//iOS8之后使用
//UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
//UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
//获取参与转场的视图
UIView *fromView = fromVC.view;
UIView *toView = toVC.view;
//获取容器视图
UIView *containerView = [transitionContext containerView];
if (toVC.isBeingPresented) {
//添加目标View
[containerView addSubview:toView];
//实现动画
toView.transform = CGAffineTransformMakeRotation(-M_PI_2); //设置初始值
//获取动画的时间
//NSTimeInterval duration = [self transitionDuration:transitionContext];
//回到默认位置
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
/**
* 正确地结束转场过程。转场的结果有两种:完成或取消
* 转场动画结束后 必须调用 否则呈现的新视图无法监听任何事件
* 非交互转场的结果只有完成一种情况,不过交互式转场需要考虑取消的情况
* 如何结束取决于转场的进度,通过transitionWasCancelled()方法来获取转场的状态,使用completeTransition:来完成或取消转场
*/
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}else{
//视图消失 Dismiss 转场中不要将 toView 添加到 containerView
if (fromVC.isBeingDismissed) {
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromView.transform = fromView.transform.b > 0.20 ? CGAffineTransformMakeRotation(M_PI_2):CGAffineTransformMakeRotation(-M_PI_2);
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
}
}
下面需要处理Modal转场
后的控制器,因为Demo实现的一个视图基于某个点旋转,所以这里使用了layer
的属性锚点anchorPoint
,对于橘色视图的旋转监听,这里采用了拖动手势UIPanGestureRecognizer
,代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
/**
* 自定义转场 这里需要设置控制器View的大小
* anchorPoint 锚点 默认是其中心点(0.5,0.5) 设置必须在frame之前
*/
self.view.backgroundColor = [UIColor orangeColor];
self.view.layer.anchorPoint = CGPointMake(0.5, 2.0);
self.view.frame = [UIScreen mainScreen].bounds;
//创建拖动手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panView:)];
[self.view addGestureRecognizer:pan];
}
处理拖动手势事件
- (void)panView:(UIPanGestureRecognizer *)pan{
switch (pan.state) {
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:{
NSLog(@"%f...",self.view.transform.b);
/**
* 根据矩阵中b的值大小操作橘色View 大于0.20实现让其掉落下来的效果 弧度为正代表顺时针
* CGAffineTransform的各种操作 旋转 平移 缩放 本质都是矩阵形式实现
*/
if (ABS(self.view.transform.b) > 0.20) {
[UIView animateWithDuration:1.0 animations:^{
self.view.transform = self.view.transform.b >0 ? CGAffineTransformMakeRotation(M_PI_2): CGAffineTransformMakeRotation(-M_PI_2);
}];
[self dismissViewControllerAnimated:YES completion:nil]; //保证视图完全移除
}else{
self.view.transform = CGAffineTransformIdentity; //默认位置
}
}
break;
default:{
//获取偏移量
CGFloat offSetX = [pan translationInView:self.view].x;
//计算偏移百分比
CGFloat percentage = offSetX /self.view.bounds.size.width;
//计算旋转的度数 这里设置旋转的度数范围 M_PI_2
CGFloat radios = percentage * M_PI_2;
//实现旋转
self.view.transform = CGAffineTransformMakeRotation(radios);
}
break;
}
}
到这里的话,Modal转场的核心基本知识就说完了,嗯…上述步骤不错的话,就能实现一个简单的自定义转场动画,其实对于自定义的转场动画,自定义容器的控制器转场应该是复杂度最高的,这里呢,先说下转场动画的简单使用吧,后面文章会更新下这个复杂知识点的,有什么理解错误的地方欢迎指正,谢谢咯。。。
附上Demo链接
参考的博文链接:http://blog.devtang.com/2016/03/13/iOS-transition-guide/