前言
最近在研究定制viewController转场,看了一篇github上的文章觉得写得很透彻(原文),是学习viewController转场的绝佳教材。这里只是做一些笔记,一方面加深理解,另一方面是将来如果忘记了可以回头看看。
视图控制器中的view显示在屏幕上有两种方式:最主要的方式是内嵌在容器控制器中,比如UINavigationController,UITabBarController, UISplitController。很多app都是UITabBarController容器装了N个UINavigationController然后每个UINavigationController里面又装了N个UIViewController。另外一种显示方式就是一个UIViewController控制显示另外一个UIViewController这总方式叫做模态显示(Modal);
转场是啥玩意儿:转场是下一场景(子VC)的视图替换当前场景(子视图)以及相应的控制器的替换,表现为当前视图消失,下一视图出现.像在UINavigationController 容器中push或者pop一个UIViewController或者tabbarController点击tab切换当前显示的controller。iOS7之后系统提供了控制viewController转场的api让我们可以定制自己的转厂动画,实现交互式转场和为自定义容器添加转场等。以前只能使用系统默认的转场.转场可以分为两种类型,非交互转场,交互式转场。如push就是一个非交互转场,而系统自带那个从屏幕左边缘滑动返回是一个交互式转场,因为我们可以通过手势去影响转场过程和进度。更准确的说是交互式转场依赖于手势等某种交互方式来完成专场过程。
下面看看如何去定制自己的转场。
uikit通过协议的方式让我们去定制转场,定制转场时候需要做的就是提供实现了这些协议的对象。其实最最核心的就是提供一个转场动画.
实现自定义转场的步骤:
1.提供一个转场代理,
告诉系统使用我们提供的代理而非默认。具体来说就是提供实现了UINavigationControllerDelegate,UITabBarControllerDelegate或者UIViewControllerTransitioningDelegate协议的对象。要定制的转场属于哪个容器的就提供一个对应的对象。进一步解释就是:当用户通过操作出发了UINavigationController或者UITabBarController转场的时候(如push,pop),容器控制器会去寻找代理对象,然后代理会按照协议提供各种转场需要的对象.动画控制器,交互控制器等..
2.动画控制器
这是定制转场中最重要的部分,负责执行动画。遵守UIViewControllerAnimatedTransitioning协议,我们实现协议的方法来提供动画。
3.交互控制器
当要实现交互式转场的时候要提供一个实现了UIViewControllerInteractiveTransitioning协议的对象提,供交互控制。系统已经打包了一个现成的类UIPercentDrivenInteractiveTransition供我们使用。
4.转场环境
遵守UIViewControllerContextTransitioning协议提供转场中需要的数据,这个对象由UIKit在转场开始前提供给前面的及动画控制器,和交互控制器使用。
5.转场协调器
遵守UIViewControllerTransitionCoordinator协议,可在转场动画发生的同时并行执行其他的动画,其作用与其说协调不如说辅助。
上面五种协议只需要关心前三个。
demo1:定制UINavigationController转场
UINavigationController转场的最简实现只要提供一个遵守UINavigationControllerDelegate的对象和一个遵守UIViewControllerAnimatedTransitioning协议的对象即可,下面上代码.
代理类是这样的:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface CustomNavigationDelegate : NSObject<UINavigationControllerDelegate>
@end
#import "CustomNavigationDelegate.h"
#import "CustomNavigationAnimatior.h"
@implementation CustomNavigationDelegate
//返回一个动画实现类
-(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
CustomNavigationAnimatior *animatior = [CustomNavigationAnimatior new];
animatior.operation = operation;
return animatior;
}
@end
动画实现类是这样的:
fromVC和toVC理解
假设有两个ViewController A 和B ,当由A转到B的时候(A的视图消失,B的视图出现)A就叫做fromVC另外一个B叫做toVC.比如从A push 到 B。同理 当由B pop 回A的时候 B是from,A是to。
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface CustomNavigationAnimatior : NSObject<UIViewControllerAnimatedTransitioning>
//push or pop
@property(assign,nonatomic)UINavigationControllerOperation operation;
@end
#import "CustomNavigationAnimatior.h"
@implementation CustomNavigationAnimatior
-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.3;
}
//提供动画具体实现
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = transitionContext.containerView;
UIView *fromView = fromVC.view;
UIView *toView = toVC.view;
CGAffineTransform toViewTransform = CGAffineTransformIdentity;
CGAffineTransform fromViewTransform = CGAffineTransformIdentity;
CGFloat translation = containerView.frame.size.width;
if (self.operation == UINavigationControllerOperationPop) {
translation *= -1;
}
toViewTransform = CGAffineTransformMakeTranslation(translation, 0);
fromViewTransform = CGAffineTransformMakeTranslation(-translation, 0);
//注意:将toview添加到containerView
[containerView addSubview:toView];
toView.transform = toViewTransform;
//这个动画就是模仿系统的push和pop,这里重点是怎样提供一个自定义的转场动画。
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromView.transform = fromViewTransform;
toView.transform = CGAffineTransformIdentity;
}completion:^(BOOL finished) {
fromView.transform = CGAffineTransformIdentity;
toView.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
}
//动画结束时调用
-(void)animationEnded:(BOOL)transitionCompleted
{
}
然后只要实例化一个CustomNavigationDelegate作为navigationController的代理就OK了,到此已经替换掉了系统的转场动画。注意这个对象在使用的时候不要被销毁,最好把他声明成navigationController的strong类型的属性。还有就是实现转场动画时候要把toView添加到containerView上。
为上面的转场添加交互,使其变为交互式转场
前面说过要实现交互,在上面的基础上要满足两个条件,1.提供一个实现了UIViewControllerInteractiveTransitioning协议的交互控制器,2.添加一个交互方式,使通过交互能够推动转场的进行。
交互控制器
在CustomNavigationDelegate中实现interactionControllerForAnimationController方法
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface CustomNavigationDelegate : NSObject<UINavigationControllerDelegate>
//用系统打包好的交互控制器类
@property (nonatomic, strong)UIPercentDrivenInteractiveTransition *interactionController;
//控制是否开启交互
@property (nonatomic, assign)BOOL interactive;
@end
#import "CustomNavigationDelegate.h"
#import "CustomNavigationAnimatior.h"
@implementation CustomNavigationDelegate
-(UIPercentDrivenInteractiveTransition *)interactionController
{
if (!_interactionController) {
_interactionController = [UIPercentDrivenInteractiveTransition new];
}
return _interactionController;
}
//返回一个动画实现类
-(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
CustomNavigationAnimatior *animatior = [CustomNavigationAnimatior new];
animatior.operation = operation;
return animatior;
}
//返回交互控制器
-(id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
//如果开启了交互但是没有动作去推进转场,试图会卡住不动,因为在等待你update转场进度但是又没有动作去推进。
//所以只有pop时候开启交互(滑动手势可控制进度),push不添加交互。interactive就是用来判断的
return self.interactive?self.interactionController:nil;
}
@end
添加交互手势
模仿系统滑动返回,添加一个手势UIScreenEdgePanGestureRecognizer。那么问题来了,这个手势加载哪里。肯定是要pop返回的那个vc的view上,不可能为每一个要滑动返回的vc重复添加手势,于是乎搞一个基类CustomBaseViewController在这里面判断下当前vc是否是容器最顶层VC,不是则添加交互手势,上代码:
#import <UIKit/UIKit.h>
@interface CustomBaseViewController : UIViewController
@end
#import "CustomBaseViewController.h"
#import "CustomNavigationDelegate.h"
@interface CustomBaseViewController ()
@property(nonatomic,strong)CustomNavigationDelegate *navDelegate;
@end
@implementation CustomBaseViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.navigationController.viewControllers.count > 1) {
//添加滑动返回手势
[self addEdgePanGes];
}
}
-(void)addEdgePanGes
{
UIScreenEdgePanGestureRecognizer *panGes = [[UIScreenEdgePanGestureRecognizer alloc]initWithTarget:self action:@selector(handleEdgePanGesture:)];
panGes.edges = UIRectEdgeLeft;
[self.view addGestureRecognizer:panGes];
}
-(void)handleEdgePanGesture:(UIScreenEdgePanGestureRecognizer *)panGes
{
CGFloat translationX = [panGes translationInView:self.view].x;
//计算百分比
CGFloat percent = fabs(translationX)/self.view.frame.size.width;
switch (panGes.state) {
case UIGestureRecognizerStateBegan:
{
_navDelegate = (CustomNavigationDelegate *)self.navigationController.delegate;
_navDelegate.interactive = YES;
//触发转场开始
[self.navigationController popViewControllerAnimated:YES];
}
break;
case UIGestureRecognizerStateChanged:
{
[_navDelegate.interactionController updateInteractiveTransition:percent];
}
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
{
if (percent>0.5) {
[_navDelegate.interactionController finishInteractiveTransition];
}else{
[_navDelegate.interactionController cancelInteractiveTransition];
}
_navDelegate.interactive = NO;
}
break;
default:
break;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
其中handleEdgePanGesture用来根据手势交互更新转场从而推进转场的进行。主要依靠UIPercentDrivenInteractiveTransition类的三个方法:
//按百分比更新转场进度
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
//取消转场使其恢复到开始状态
- (void)cancelInteractiveTransition;
//直接跳到转场结束状态
- (void)finishInteractiveTransition;
这个类是系统打包好的,他遵守UIViewControllerInteractiveTransitioning协议,必要的时候可以遵守这个协议自己去实现一个。
demo2:定制UITabBarController转场
方法和demo1差不多,就是实现代理,在代理中返回动画控制器,交互控制器。然后在添加一个用于交互的pan手势.
上代码:
首先继承UITabBarController创建个子类EDTabBarController我们要在这里面添加手势和代理。
#import <UIKit/UIKit.h>
@interface EDTabBarController : UITabBarController
@end
#import "EDTabBarController.h"
#import "EDTabBarDelegate.h"
@interface EDTabBarController ()
@property(nonatomic,strong)EDTabBarDelegate *tabbarDelegate;
@end
@implementation EDTabBarController
- (void)viewDidLoad {
[super viewDidLoad];
//添加代理和手势
self.delegate = self.tabbarDelegate;
UIPanGestureRecognizer *panGes = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handlePan:)];
[self.view addGestureRecognizer:panGes];
}
-(EDTabBarDelegate *)tabbarDelegate
{
if (!_tabbarDelegate) {
_tabbarDelegate = [EDTabBarDelegate new];
}
return _tabbarDelegate;
}
-(void)handlePan:(UIPanGestureRecognizer *)panGes
{
//计算进度,然后开始,更新,结束或取消
CGFloat translationX = [panGes translationInView:self.view].x;
CGFloat percent = fabs(translationX)/self.view.frame.size.width;
switch (panGes.state) {
case UIGestureRecognizerStateBegan:
{
//通过设置selectedIndex触发开始转场(如何判断应该显示前一个还是后一个)
//velocityX的绝对值代表手指在view上的移动速度,正负号代表方向。
//正:手指向右滑动,也就是要显示比当前index小的VC
//负:与上面相反
CGFloat velocityX = [panGes velocityInView:self.view].x;
if (velocityX > 0) {
if (self.selectedIndex > 0) {
self.tabbarDelegate.interactive = YES;
self.selectedIndex -= 1;
}
}else{
if (self.selectedIndex < self.viewControllers.count - 1) {
self.tabbarDelegate.interactive = YES;
self.selectedIndex += 1;
}
}
}
break;
case UIGestureRecognizerStateChanged:
{
//更新转场进度
[self.tabbarDelegate.interactionController updateInteractiveTransition:percent];
}
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
{
self.tabbarDelegate.interactionController.completionSpeed = 0.99;
if (percent > 0.3) {
[self.tabbarDelegate.interactionController finishInteractiveTransition];
}else{
[self.tabbarDelegate.interactionController cancelInteractiveTransition];
}
self.tabbarDelegate.interactive = NO;
}
break;
default:
break;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
代理类
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface EDTabBarDelegate : NSObject<UITabBarControllerDelegate>
@property (nonatomic, strong)UIPercentDrivenInteractiveTransition *interactionController;
@property (nonatomic, assign)BOOL interactive;
@end
#import "EDTabBarDelegate.h"
#import "EDTabbarTransitionAnimator.h"
@implementation EDTabBarDelegate
//返回动画控制器
-(id<UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
NSInteger fromIndex = [tabBarController.viewControllers indexOfObject:fromVC];
NSInteger toIndex = [tabBarController.viewControllers indexOfObject:toVC];
TabOperationDirection operation = fromIndex > toIndex?Left:Right;
EDTabbarTransitionAnimator *animator = [EDTabbarTransitionAnimator new];
animator.operation = operation;
return animator;
}
//返回交互控制器
-(id<UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
return self.interactive?self.interactionController:nil;
}
-(UIPercentDrivenInteractiveTransition *)interactionController
{
if (!_interactionController) {
_interactionController = [UIPercentDrivenInteractiveTransition new];
}
return _interactionController;
}
动画控制器
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger,TabOperationDirection){
Left = 0,//像左滚动
Right //向右滚动
};
@interface EDTabbarTransitionAnimator : NSObject<UIViewControllerAnimatedTransitioning>
@property (nonatomic, assign) TabOperationDirection operation;
@end
#import "EDTabbarTransitionAnimator.h"
@implementation EDTabbarTransitionAnimator
-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.3;
}
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = transitionContext.containerView;
UIView *fromView = fromVC.view;
UIView *toView = toVC.view;
CGAffineTransform toViewTransform = CGAffineTransformIdentity;
CGAffineTransform fromViewTransform = CGAffineTransformIdentity;
CGFloat translation = containerView.frame.size.width;
if (self.operation == Left) {
translation *= -1;
}
toViewTransform = CGAffineTransformMakeTranslation(translation, 0);
fromViewTransform = CGAffineTransformMakeTranslation(-translation, 0);
//将toview添加到containerView
[containerView addSubview:toView];
//动画很简单 就是两个视图的平移
toView.transform = toViewTransform;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromView.transform = fromViewTransform;
toView.transform = CGAffineTransformIdentity;
}completion:^(BOOL finished) {
fromView.transform = CGAffineTransformIdentity;
toView.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
}
-(void)animationEnded:(BOOL)transitionCompleted
{
//动画结束后调用该方法
}
@end
到此定制UITabBarController转场完成,和定制UINavigationController转场差不多。
demo3:定制Modal转场
modal转场就是persent和didmiss转场。Modal 转场有多种模式,由其modalPresentationStyle属性决定,有两种模式可以进行自定义: UIModalPresentationFullScreen 模式(默认值)和 UIModalPresentationCustom 模式。
Modal转场和上面两种转场有点儿差别。上面两种转场中fromVC和toVC都是处在同一个容器中,但是在model转场中并没有容器这玩意儿。还有一个不同就是以上两种转场方式在转场开始时候fromVC会被自动加入containerView,结束后会自动被移除。但是modal转场如果是UIModalPresentationCustom模式下fromView在转场结束后不会被移除。
上图是modal转场的视图层次区别。
从图中可以看出
modal转场中两个视图不处于同一层次,present的时候toView也就是presentedView要主动加到containerView中,但是dismiss的时候toView是presentingView,他不需要添加到containerView中。这个很重要。
modal转场的实现也和上面一个套路,1.提供代理 2.提供动画控制器.
上代码
动画控制器
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface EDModalAnimator : NSObject<UIViewControllerAnimatedTransitioning>
@end
#import "EDModalAnimator.h"
@implementation EDModalAnimator
-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.3;
}
//实现了一个缩放动画,要注意在Custom模式下dismiss时候不要将toView添加到containerView上
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = transitionContext.containerView;
UIView *fromView = fromVC.view;
UIView *toView = toVC.view;
//present
if (toVC.isBeingPresented) {
[containerView addSubview:toView];
toView.center = containerView.center;
toView.bounds = CGRectMake(0, 0, containerView.frame.size.width*2/3, containerView.frame.size.height*2/3);
toView.transform = CGAffineTransformMakeScale(0.1, 0.1);
[UIView animateWithDuration:0.3 animations:^{
toView.transform = CGAffineTransformMakeScale(1, 1);
} completion:^(BOOL finished) {
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
}
//Modal 转场在 Custom 模式下必须区分 presentation 和 dismissal 转场(此时 dismissal 转场中不要将 toView 添加到 containerView)
if (fromVC.beingDismissed) {
if (transitionContext.presentationStyle != UIModalPresentationCustom) {
[containerView addSubview:toView];
}
[UIView animateWithDuration:0.3 animations:^{
fromView.transform = CGAffineTransformMakeScale(0.1, 0.1);
} completion:^(BOOL finished) {
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
}
}
@end
代理类
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface ModelTransitionDelegate : NSObject<UIViewControllerTransitioningDelegate>
@end
#import "ModelTransitionDelegate.h"
#import "EDPressentationController.h"
#import "EDModalAnimator.h"
@implementation ModelTransitionDelegate
//出现时的动画
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
return [EDModalAnimator new];
}
//关闭时的动画
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
return [EDModalAnimator new];
}
-(UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source
{
return [[EDPressentationController alloc]initWithPresentedViewController:presented presentingViewController:presenting];
}
Presenting类和Presented类
#import <UIKit/UIKit.h>
@interface PresentingVC : UIViewController
@end
#import "PresentingVC.h"
#import "ModelTransitionDelegate.h"
#import "PresentedVC.h"
@interface PresentingVC ()
@property(nonatomic,strong)ModelTransitionDelegate *transitionDelegate;
@end
@implementation PresentingVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorWithRed:255.0/255 green:83.0/255 blue:89.0/255 alpha:1];
UIButton *presentBtn = [[UIButton alloc]initWithFrame:CGRectMake(146.5, 313, 82, 41)];
[presentBtn setTitle:@"Present" forState:UIControlStateNormal];
[presentBtn addTarget:self action:@selector(goNext:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:presentBtn];
}
-(id<UIViewControllerTransitioningDelegate>)transitioningDelegate
{
if (!_transitionDelegate) {
_transitionDelegate = [ModelTransitionDelegate new];
}
return _transitionDelegate;
}
-(void)goNext:(UIButton *)sender
{
PresentedVC *nextVC = [PresentedVC new];
nextVC.transitioningDelegate = self.transitioningDelegate;
nextVC.modalPresentationStyle = UIModalPresentationCustom;
[self presentViewController:nextVC animated:YES completion:nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
#import <UIKit/UIKit.h>
@interface PresentedVC : UIViewController
@end
#import "PresentedVC.h"
@interface PresentedVC ()
@end
@implementation PresentedVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blueColor];
UIButton *closeBtn = [[UIButton alloc]initWithFrame:CGRectMake(100, 200, 50, 30)];
[closeBtn setTitle:@"Close" forState:UIControlStateNormal];
[closeBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[closeBtn addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:closeBtn];
}
-(void)dismiss
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
代理类的最后有个EDPressentationController他是UIPresentationController的子类,iOS8之后添加的东西,前面多次在动画控制器中用到一个contanierView.打断点可以看到他是一个UITansitionView。其实他就是EDPressentationController的view。
ios8之后提供了UIPresentationController类,让我们可以在contanierView上做些事情,利用转场协调器还可以和动画控制器同步进行动画,比如添加一个灰色半透明背景。他看起来是这样的:
#import <UIKit/UIKit.h>
@interface EDPressentationController : UIPresentationController
@end
#import "EDPressentationController.h"
@interface EDPressentationController()
@property(nonatomic,strong)UIView *backView;
@end
@implementation EDPressentationController
-(UIView *)backView
{
if (!_backView) {
_backView = [[UIView alloc]init];
_backView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.2];
_backView.alpha = 0;
}
return _backView;
}
//Presentation 转场开始前该方法被调用。
-(void)presentationTransitionWillBegin
{
[self.containerView addSubview:self.backView];
[self.presentedViewController.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
//这里的动画与专场动画同步执行
self.backView.alpha = 1;
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
}];
}
//Dismissal 转场开始前该方法被调用。
-(void)dismissalTransitionWillBegin
{
[self.presentedViewController.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
self.backView.alpha = 0;
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
}];
}
//iOS 8 带来了适应性布局,<UIContentContainer>协议用于响应视图尺寸变化和屏幕旋转事件,之前用于处理屏幕旋转的方法都被废弃了。UIViewController 和 UIPresentationController 类都遵守该协议。
//这个方法负责布局
-(void)containerViewWillLayoutSubviews
{
self.backView.center = self.containerView.center;
self.backView.bounds = self.containerView.bounds;
}
//转场后presentingView是否被移除 默认NO
-(BOOL)shouldRemovePresentersView
{
return NO;
}
@end
UIPresentationController
这个玩意儿有必要单独说下,他是ios8以后提供的api。他的作用是用于展示一个controller,其实modal方式去展示一个controller就是用的这个玩意儿,转场时候可以通过定制这个类的子类,然后在转场协议中提供他,转场的时候就会用这个自定义的,而不是系统默认的。这样就可以做到:
1.定制presentedView尺寸以及在containerView中添加自定会视图并为这些视图添加动画。
2.可以选择是否移除presentingView。
3.可以在没有动画控制器的情况下单独工作,这个准确说是当我们不提供动画控制器的时候,他会用系统自带的。
结束
到这里官方支持的定制转场已经结束,但是还有个问题没有解决,就是如何为自定义的ViewController容器添加转场。
这个自定义容器不继承系统提供的容器(如UITabBarController),他直接继承UIViewController。这也是我研究控制器转场的目的。希望能够自己实现比较靠谱的controler容器,从而增加app的体验,使之区别于一般app的navigation加tabbar的这种app架构。