定制viewController轉(zhuǎn)場(chǎng)

前言

最近在研究定制viewController轉(zhuǎn)場(chǎng)诬滩,看了一篇github上的文章覺得寫得很透徹(原文)疼鸟,是學(xué)習(xí)viewController轉(zhuǎn)場(chǎng)的絕佳教材。這里只是做一些筆記浩淘,一方面加深理解张抄,另一方面是將來如果忘記了可以回頭看看署惯。

視圖控制器中的view顯示在屏幕上有兩種方式:最主要的方式是內(nèi)嵌在容器控制器中极谊,比如UINavigationController安岂,UITabBarController, UISplitController域那。很多app都是UITabBarController容器裝了N個(gè)UINavigationController然后每個(gè)UINavigationController里面又裝了N個(gè)UIViewController琉雳。另外一種顯示方式就是一個(gè)UIViewController控制顯示另外一個(gè)UIViewController這總方式叫做模態(tài)顯示(Modal);
轉(zhuǎn)場(chǎng)是啥玩意兒:轉(zhuǎn)場(chǎng)是下一場(chǎng)景(子VC)的視圖替換當(dāng)前場(chǎng)景(子視圖)以及相應(yīng)的控制器的替換翠肘,表現(xiàn)為當(dāng)前視圖消失束倍,下一視圖出現(xiàn).像在UINavigationController 容器中push或者pop一個(gè)UIViewController或者tabbarController點(diǎn)擊tab切換當(dāng)前顯示的controller绪妹。iOS7之后系統(tǒng)提供了控制viewController轉(zhuǎn)場(chǎng)的api讓我們可以定制自己的轉(zhuǎn)廠動(dòng)畫柿究,實(shí)現(xiàn)交互式轉(zhuǎn)場(chǎng)和為自定義容器添加轉(zhuǎn)場(chǎng)等蝇摸。以前只能使用系統(tǒng)默認(rèn)的轉(zhuǎn)場(chǎng).轉(zhuǎn)場(chǎng)可以分為兩種類型,非交互轉(zhuǎn)場(chǎng)民镜,交互式轉(zhuǎn)場(chǎng)制圈。如push就是一個(gè)非交互轉(zhuǎn)場(chǎng),而系統(tǒng)自帶那個(gè)從屏幕左邊緣滑動(dòng)返回是一個(gè)交互式轉(zhuǎn)場(chǎng)鲸鹦,因?yàn)槲覀兛梢酝ㄟ^手勢(shì)去影響轉(zhuǎn)場(chǎng)過程和進(jìn)度亥鬓。更準(zhǔn)確的說是交互式轉(zhuǎn)場(chǎng)依賴于手勢(shì)等某種交互方式來完成專場(chǎng)過程域庇。
下面看看如何去定制自己的轉(zhuǎn)場(chǎng)听皿。
uikit通過協(xié)議的方式讓我們?nèi)ザㄖ妻D(zhuǎn)場(chǎng)尉姨,定制轉(zhuǎn)場(chǎng)時(shí)候需要做的就是提供實(shí)現(xiàn)了這些協(xié)議的對(duì)象又厉。其實(shí)最最核心的就是提供一個(gè)轉(zhuǎn)場(chǎng)動(dòng)畫.

實(shí)現(xiàn)自定義轉(zhuǎn)場(chǎng)的步驟:

1.提供一個(gè)轉(zhuǎn)場(chǎng)代理覆致,

告訴系統(tǒng)使用我們提供的代理而非默認(rèn)煌妈。具體來說就是提供實(shí)現(xiàn)了UINavigationControllerDelegate璧诵,UITabBarControllerDelegate或者UIViewControllerTransitioningDelegate協(xié)議的對(duì)象。要定制的轉(zhuǎn)場(chǎng)屬于哪個(gè)容器的就提供一個(gè)對(duì)應(yīng)的對(duì)象族操。進(jìn)一步解釋就是:當(dāng)用戶通過操作出發(fā)了UINavigationController或者UITabBarController轉(zhuǎn)場(chǎng)的時(shí)候(如push色难,pop),容器控制器會(huì)去尋找代理對(duì)象莱预,然后代理會(huì)按照協(xié)議提供各種轉(zhuǎn)場(chǎng)需要的對(duì)象.動(dòng)畫控制器依沮,交互控制器等..

2.動(dòng)畫控制器

這是定制轉(zhuǎn)場(chǎng)中最重要的部分,負(fù)責(zé)執(zhí)行動(dòng)畫危喉。遵守UIViewControllerAnimatedTransitioning協(xié)議辜限,我們實(shí)現(xiàn)協(xié)議的方法來提供動(dòng)畫薄嫡。

3.交互控制器

當(dāng)要實(shí)現(xiàn)交互式轉(zhuǎn)場(chǎng)的時(shí)候要提供一個(gè)實(shí)現(xiàn)了UIViewControllerInteractiveTransitioning協(xié)議的對(duì)象提毫深,供交互控制哑蔫。系統(tǒng)已經(jīng)打包了一個(gè)現(xiàn)成的類UIPercentDrivenInteractiveTransition供我們使用闸迷。

4.轉(zhuǎn)場(chǎng)環(huán)境

遵守UIViewControllerContextTransitioning協(xié)議提供轉(zhuǎn)場(chǎng)中需要的數(shù)據(jù)腥沽,這個(gè)對(duì)象由UIKit在轉(zhuǎn)場(chǎng)開始前提供給前面的及動(dòng)畫控制器巡球,和交互控制器使用酣栈。

5.轉(zhuǎn)場(chǎng)協(xié)調(diào)器

遵守UIViewControllerTransitionCoordinator協(xié)議矿筝,可在轉(zhuǎn)場(chǎng)動(dòng)畫發(fā)生的同時(shí)并行執(zhí)行其他的動(dòng)畫棚贾,其作用與其說協(xié)調(diào)不如說輔助。
上面五種協(xié)議只需要關(guān)心前三個(gè)鼻疮。

demo1:定制UINavigationController轉(zhuǎn)場(chǎng)

UINavigationController轉(zhuǎn)場(chǎng)的最簡(jiǎn)實(shí)現(xiàn)只要提供一個(gè)遵守UINavigationControllerDelegate的對(duì)象和一個(gè)遵守UIViewControllerAnimatedTransitioning協(xié)議的對(duì)象即可,下面上代碼.
代理類是這樣的:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface CustomNavigationDelegate : NSObject<UINavigationControllerDelegate>

@end

#import "CustomNavigationDelegate.h"
#import "CustomNavigationAnimatior.h"
@implementation CustomNavigationDelegate

//返回一個(gè)動(dòng)畫實(shí)現(xiàn)類
-(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
    CustomNavigationAnimatior *animatior = [CustomNavigationAnimatior new];
    animatior.operation = operation;
    return animatior;
}
@end

動(dòng)畫實(shí)現(xiàn)類是這樣的:

fromVC和toVC理解

假設(shè)有兩個(gè)ViewController A 和B 挪哄,當(dāng)由A轉(zhuǎn)到B的時(shí)候(A的視圖消失迹炼,B的視圖出現(xiàn))A就叫做fromVC另外一個(gè)B叫做toVC.比如從A push 到 B斯入。同理 當(dāng)由B pop 回A的時(shí)候 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;
}

//提供動(dòng)畫具體實(shí)現(xiàn)
-(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;
    //這個(gè)動(dòng)畫就是模仿系統(tǒng)的push和pop,這里重點(diǎn)是怎樣提供一個(gè)自定義的轉(zhuǎn)場(chǎng)動(dòng)畫厅克。
    [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];
    }];
    
}

//動(dòng)畫結(jié)束時(shí)調(diào)用
-(void)animationEnded:(BOOL)transitionCompleted
{
    
}

然后只要實(shí)例化一個(gè)CustomNavigationDelegate作為navigationController的代理就OK了证舟,到此已經(jīng)替換掉了系統(tǒng)的轉(zhuǎn)場(chǎng)動(dòng)畫女责。注意這個(gè)對(duì)象在使用的時(shí)候不要被銷毀抵知,最好把他聲明成navigationController的strong類型的屬性刷喜。還有就是實(shí)現(xiàn)轉(zhuǎn)場(chǎng)動(dòng)畫時(shí)候要把toView添加到containerView上掖疮。

為上面的轉(zhuǎn)場(chǎng)添加交互浊闪,使其變?yōu)榻换ナ睫D(zhuǎn)場(chǎng)

前面說過要實(shí)現(xiàn)交互,在上面的基礎(chǔ)上要滿足兩個(gè)條件规揪,1.提供一個(gè)實(shí)現(xiàn)了UIViewControllerInteractiveTransitioning協(xié)議的交互控制器,2.添加一個(gè)交互方式猛铅,使通過交互能夠推動(dòng)轉(zhuǎn)場(chǎng)的進(jìn)行奸忽。

交互控制器

在CustomNavigationDelegate中實(shí)現(xiàn)interactionControllerForAnimationController方法

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface CustomNavigationDelegate : NSObject<UINavigationControllerDelegate>
//用系統(tǒng)打包好的交互控制器類
@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;
}
//返回一個(gè)動(dòng)畫實(shí)現(xiàn)類
-(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
{
    //如果開啟了交互但是沒有動(dòng)作去推進(jìn)轉(zhuǎn)場(chǎng),試圖會(huì)卡住不動(dòng)欠雌,因?yàn)樵诘却鉼pdate轉(zhuǎn)場(chǎng)進(jìn)度但是又沒有動(dòng)作去推進(jìn)富俄。
    //所以只有pop時(shí)候開啟交互(滑動(dòng)手勢(shì)可控制進(jìn)度)而咆,push不添加交互暴备。interactive就是用來判斷的
    return self.interactive?self.interactionController:nil;
}
@end
添加交互手勢(shì)

模仿系統(tǒng)滑動(dòng)返回涯捻,添加一個(gè)手勢(shì)UIScreenEdgePanGestureRecognizer障癌。那么問題來了混弥,這個(gè)手勢(shì)加載哪里蝗拿“校肯定是要pop返回的那個(gè)vc的view上仓手,不可能為每一個(gè)要滑動(dòng)返回的vc重復(fù)添加手勢(shì)嗽冒,于是乎搞一個(gè)基類CustomBaseViewController在這里面判斷下當(dāng)前vc是否是容器最頂層VC添坊,不是則添加交互手勢(shì)贬蛙,上代碼:

#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) {
        //添加滑動(dòng)返回手勢(shì)
        [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;
    //計(jì)算百分比
    CGFloat percent      = fabs(translationX)/self.view.frame.size.width;
    
    switch (panGes.state) {
        case UIGestureRecognizerStateBegan:
        {
            _navDelegate = (CustomNavigationDelegate *)self.navigationController.delegate;
            _navDelegate.interactive  = YES;
          //觸發(fā)轉(zhuǎn)場(chǎng)開始
            [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用來根據(jù)手勢(shì)交互更新轉(zhuǎn)場(chǎng)從而推進(jìn)轉(zhuǎn)場(chǎng)的進(jìn)行氛堕。主要依靠UIPercentDrivenInteractiveTransition類的三個(gè)方法:

//按百分比更新轉(zhuǎn)場(chǎng)進(jìn)度
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
//取消轉(zhuǎn)場(chǎng)使其恢復(fù)到開始狀態(tài)
- (void)cancelInteractiveTransition;
//直接跳到轉(zhuǎn)場(chǎng)結(jié)束狀態(tài)
- (void)finishInteractiveTransition;

這個(gè)類是系統(tǒng)打包好的,他遵守UIViewControllerInteractiveTransitioning協(xié)議乱灵,必要的時(shí)候可以遵守這個(gè)協(xié)議自己去實(shí)現(xiàn)一個(gè)痛倚。

demo2:定制UITabBarController轉(zhuǎn)場(chǎng)

方法和demo1差不多,就是實(shí)現(xiàn)代理蝉稳,在代理中返回動(dòng)畫控制器耘戚,交互控制器收津。然后在添加一個(gè)用于交互的pan手勢(shì).
上代碼:
首先繼承UITabBarController創(chuàng)建個(gè)子類EDTabBarController我們要在這里面添加手勢(shì)和代理撞秋。

#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];
    //添加代理和手勢(shì)
    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
{
  //計(jì)算進(jìn)度吻贿,然后開始舅列,更新帐要,結(jié)束或取消
    CGFloat    translationX =  [panGes translationInView:self.view].x;
    CGFloat    percent      = fabs(translationX)/self.view.frame.size.width;
    
    switch (panGes.state) {
        case UIGestureRecognizerStateBegan:
        {
            //通過設(shè)置selectedIndex觸發(fā)開始轉(zhuǎn)場(chǎng)(如何判斷應(yīng)該顯示前一個(gè)還是后一個(gè))
            //velocityX的絕對(duì)值代表手指在view上的移動(dòng)速度榨惠,正負(fù)號(hào)代表方向冒冬。
            //正:手指向右滑動(dòng)简烤,也就是要顯示比當(dāng)前index小的VC
            //負(fù):與上面相反
            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:
        {
            //更新轉(zhuǎn)場(chǎng)進(jìn)度
            [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
//返回動(dòng)畫控制器
-(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;
}

動(dòng)畫控制器

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef  NS_ENUM(NSInteger,TabOperationDirection){
   Left = 0,//像左滾動(dòng)
   Right    //向右滾動(dòng)
};
@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];
   
//動(dòng)畫很簡(jiǎn)單 就是兩個(gè)視圖的平移
   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
{
   //動(dòng)畫結(jié)束后調(diào)用該方法
}
@end

到此定制UITabBarController轉(zhuǎn)場(chǎng)完成引瀑,和定制UINavigationController轉(zhuǎn)場(chǎng)差不多憨栽。

demo3:定制Modal轉(zhuǎn)場(chǎng)

modal轉(zhuǎn)場(chǎng)就是persent和didmiss轉(zhuǎn)場(chǎng)屑柔。Modal 轉(zhuǎn)場(chǎng)有多種模式掸宛,由其modalPresentationStyle屬性決定招拙,有兩種模式可以進(jìn)行自定義: UIModalPresentationFullScreen 模式(默認(rèn)值)和 UIModalPresentationCustom 模式饰序。
Modal轉(zhuǎn)場(chǎng)和上面兩種轉(zhuǎn)場(chǎng)有點(diǎn)兒差別。上面兩種轉(zhuǎn)場(chǎng)中fromVC和toVC都是處在同一個(gè)容器中掠械,但是在model轉(zhuǎn)場(chǎng)中并沒有容器這玩意兒均唉。還有一個(gè)不同就是以上兩種轉(zhuǎn)場(chǎng)方式在轉(zhuǎn)場(chǎng)開始時(shí)候fromVC會(huì)被自動(dòng)加入containerView舔箭,結(jié)束后會(huì)自動(dòng)被移除。但是modal轉(zhuǎn)場(chǎng)如果是UIModalPresentationCustom模式下fromView在轉(zhuǎn)場(chǎng)結(jié)束后不會(huì)被移除箫章。

ContainerVC VS Modal.png

上圖是modal轉(zhuǎn)場(chǎng)的視圖層次區(qū)別。

從圖中可以看出
modal轉(zhuǎn)場(chǎng)中兩個(gè)視圖不處于同一層次,present的時(shí)候toView也就是presentedView要主動(dòng)加到containerView中镜会,但是dismiss的時(shí)候toView是presentingView檬寂,他不需要添加到containerView中。這個(gè)很重要戳表。

modal轉(zhuǎn)場(chǎng)的實(shí)現(xiàn)也和上面一個(gè)套路桶至,1.提供代理 2.提供動(dòng)畫控制器.
上代碼

動(dòng)畫控制器
#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;
}
//實(shí)現(xiàn)了一個(gè)縮放動(dòng)畫,要注意在Custom模式下dismiss時(shí)候不要將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 轉(zhuǎn)場(chǎng)在 Custom 模式下必須區(qū)分 presentation 和 dismissal 轉(zhuǎn)場(chǎng)(此時(shí) dismissal 轉(zhuǎn)場(chǎng)中不要將 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

//出現(xiàn)時(shí)的動(dòng)畫
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
    return [EDModalAnimator new];
}
//關(guān)閉時(shí)的動(dòng)畫
-(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

代理類的最后有個(gè)EDPressentationController他是UIPresentationController的子類镣屹,iOS8之后添加的東西,前面多次在動(dòng)畫控制器中用到一個(gè)contanierView.打斷點(diǎn)可以看到他是一個(gè)UITansitionView价涝。其實(shí)他就是EDPressentationController的view女蜈。
ios8之后提供了UIPresentationController類,讓我們可以在contanierView上做些事情色瘩,利用轉(zhuǎn)場(chǎng)協(xié)調(diào)器還可以和動(dòng)畫控制器同步進(jìn)行動(dòng)畫鞭光,比如添加一個(gè)灰色半透明背景。他看起來是這樣的:

#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 轉(zhuǎn)場(chǎng)開始前該方法被調(diào)用泞遗。
-(void)presentationTransitionWillBegin
{
    [self.containerView addSubview:self.backView];
    [self.presentedViewController.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        //這里的動(dòng)畫與專場(chǎng)動(dòng)畫同步執(zhí)行
        self.backView.alpha = 1;
    } completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        
    }];
}

//Dismissal 轉(zhuǎn)場(chǎng)開始前該方法被調(diào)用惰许。
-(void)dismissalTransitionWillBegin
{
    [self.presentedViewController.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        self.backView.alpha = 0;
        
    } completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        
    }];
}
//iOS 8 帶來了適應(yīng)性布局,<UIContentContainer>協(xié)議用于響應(yīng)視圖尺寸變化和屏幕旋轉(zhuǎn)事件史辙,之前用于處理屏幕旋轉(zhuǎn)的方法都被廢棄了汹买。UIViewController 和 UIPresentationController 類都遵守該協(xié)議佩伤。
//這個(gè)方法負(fù)責(zé)布局
-(void)containerViewWillLayoutSubviews
{
    self.backView.center = self.containerView.center;
    self.backView.bounds = self.containerView.bounds;
    
}

//轉(zhuǎn)場(chǎng)后presentingView是否被移除  默認(rèn)NO
-(BOOL)shouldRemovePresentersView
{
    return NO;
}

@end

UIPresentationController

這個(gè)玩意兒有必要單獨(dú)說下,他是ios8以后提供的api晦毙。他的作用是用于展示一個(gè)controller,其實(shí)modal方式去展示一個(gè)controller就是用的這個(gè)玩意兒生巡,轉(zhuǎn)場(chǎng)時(shí)候可以通過定制這個(gè)類的子類,然后在轉(zhuǎn)場(chǎng)協(xié)議中提供他见妒,轉(zhuǎn)場(chǎng)的時(shí)候就會(huì)用這個(gè)自定義的孤荣,而不是系統(tǒng)默認(rèn)的。這樣就可以做到:
1.定制presentedView尺寸以及在containerView中添加自定會(huì)視圖并為這些視圖添加動(dòng)畫须揣。
2.可以選擇是否移除presentingView盐股。
3.可以在沒有動(dòng)畫控制器的情況下單獨(dú)工作,這個(gè)準(zhǔn)確說是當(dāng)我們不提供動(dòng)畫控制器的時(shí)候耻卡,他會(huì)用系統(tǒng)自帶的疯汁。

結(jié)束

到這里官方支持的定制轉(zhuǎn)場(chǎng)已經(jīng)結(jié)束,但是還有個(gè)問題沒有解決,就是如何為自定義的ViewController容器添加轉(zhuǎn)場(chǎng)卵酪。
這個(gè)自定義容器不繼承系統(tǒng)提供的容器(如UITabBarController),他直接繼承UIViewController幌蚊。這也是我研究控制器轉(zhuǎn)場(chǎng)的目的。希望能夠自己實(shí)現(xiàn)比較靠譜的controler容器溃卡,從而增加app的體驗(yàn)溢豆,使之區(qū)別于一般app的navigation加tabbar的這種app架構(gòu)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘸羡,一起剝皮案震驚了整個(gè)濱河市漩仙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌最铁,老刑警劉巖讯赏,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異冷尉,居然都是意外死亡漱挎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門雀哨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來磕谅,“玉大人,你說我怎么就攤上這事雾棺〔布校” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵捌浩,是天一觀的道長(zhǎng)放刨。 經(jīng)常有香客問我,道長(zhǎng)尸饺,這世上最難降的妖魔是什么进统? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任助币,我火速辦了婚禮,結(jié)果婚禮上螟碎,老公的妹妹穿的比我還像新娘眉菱。我一直安慰自己,他們只是感情好掉分,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布俭缓。 她就那樣靜靜地躺著,像睡著了一般酥郭。 火紅的嫁衣襯著肌膚如雪华坦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天褥民,我揣著相機(jī)與錄音季春,去河邊找鬼洗搂。 笑死消返,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的耘拇。 我是一名探鬼主播撵颊,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼惫叛!你這毒婦竟也來了倡勇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤嘉涌,失蹤者是張志新(化名)和其女友劉穎妻熊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仑最,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扔役,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了警医。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亿胸。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖预皇,靈堂內(nèi)的尸體忽然破棺而出侈玄,到底是詐尸還是另有隱情,我是刑警寧澤吟温,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布序仙,位于F島的核電站,受9級(jí)特大地震影響鲁豪,放射性物質(zhì)發(fā)生泄漏潘悼。R本人自食惡果不足惜洋丐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挥等。 院中可真熱鬧友绝,春花似錦橄唬、人聲如沸隙券。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荠割。三九已至咏删,卻和暖如春秦士,著一層夾襖步出監(jiān)牢的瞬間马昙,已是汗流浹背榄檬。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工卜范, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鹿榜。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓海雪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親舱殿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奥裸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 前言的前言 唐巧前輩在微信公眾號(hào)「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各項(xiàng)指標(biāo)...
    VincentHK閱讀 5,362評(píng)論 3 44
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,099評(píng)論 25 707
  • iOS視圖控制器詳解 視圖控制器中的視圖顯示在屏幕上有兩種方式:最主要的方式是內(nèi)嵌在容器控制器中沪袭,比如 UINav...
    coder_feng閱讀 11,224評(píng)論 2 12
  • 人真的會(huì)一時(shí)一變湾宙,可能是戀愛把我變得多愁善感了,我并不能時(shí)時(shí)對(duì)你都有一樣的熱情冈绊。所以很難過侠鳄,怕終有一天,我們還是會(huì)...
    我和我的小太陽閱讀 93評(píng)論 0 0
  • 北極熊在冰原上行走 毛皮掛在身上 撐著嶙峋的骨架 那些脂肪都去哪了 曾經(jīng)的冰天雪地 如今都淪為碎冰漂浮 一只雪蠶張...
    一團(tuán)菌閱讀 110評(píng)論 0 2