定制viewController转场

前言

最近在研究定制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在转场结束后不会被移除。

ContainerVC VS Modal.png

上图是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架构。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 前言的前言 唐巧前辈在微信公众号「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各项指标...
    VincentHK阅读 5,241评论 4 44
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • iOS视图控制器详解 视图控制器中的视图显示在屏幕上有两种方式:最主要的方式是内嵌在容器控制器中,比如 UINav...
    coder_feng阅读 11,084评论 2 12
  • 人真的会一时一变,可能是恋爱把我变得多愁善感了,我并不能时时对你都有一样的热情。所以很难过,怕终有一天,我们还是会...
    我和我的小太阳阅读 78评论 0 0
  • 北极熊在冰原上行走 毛皮挂在身上 撑着嶙峋的骨架 那些脂肪都去哪了 曾经的冰天雪地 如今都沦为碎冰漂浮 一只雪蚕张...
    一团菌阅读 95评论 0 2