UIVIewController自定義切換效果

以前介紹動畫時提過UIView的轉場動畫,可是開發中咱們碰到更多的viewController的切換,ios中常見的viewcontroller切換有四種:模態視圖,導航欄控制器,UITabBarController以及addchildviewcontroller,自定義viewcontroller動畫切換也是ios7中的新特性,這裏整理下常見的操做,outline以下(本文參考http://onevcat.com/2013/10/vc-transition-in-ios7/,代碼下載地址爲https://github.com/zanglitao/UIVIewControllerSwitchhtml

1:基本介紹ios

2:模態視圖自定義動畫切換git

3:UINavigationController自定義動畫切換github

4:UITaBarController自定義動畫切換app

5:模態視圖交互式動畫切換iview

6:UINavigationController交互式動畫切換ide

 

基本介紹動畫

在深刻以前,咱們先來看看新SDK中有關這部份內容的相關接口以及它們的關係和典型用法。這幾個接口和類的名字都比較類似,可是仍是能比較好的描述出各自的職能的,一開始的話可能比較迷惑,可是當本身動手實現一兩個例子以後,它們之間的關係就會逐漸明晰起來。(相關的內容都定義在UIKit的UIViewControllerTransitioning.h中了)ui

@protocol UIViewControllerContextTransitioning

這個接口用來提供切換上下文給開發者使用,包含了從哪一個VC到哪一個VC等各種信息,通常不須要開發者本身實現。具體來講,iOS7的自定義切換目的之一就是切換相關代碼解耦,在進行VC切換時,作切換效果實現的時候必需要須要切換先後VC的一些信息,系統在新加入的API的比較的地方都會提供一個實現了該接口的對象,以供咱們使用。atom

對於切換的動畫實現來講(這裏先介紹簡單的動畫,在後面我會再引入手勢驅動的動畫),這個接口中最重要的方法有:

  • -(UIView *)containerView; VC切換所發生的view容器,開發者應該將切出的view移除,將切入的view加入到該view容器中。
  • -(UIViewController *)viewControllerForKey:(NSString *)key; 提供一個key,返回對應的VC。如今的SDK中key的選擇只有UITransitionContextFromViewControllerKey和UITransitionContextToViewControllerKey兩種,分別表示將要切出和切入的VC。
  • -(CGRect)initialFrameForViewController:(UIViewController *)vc; 某個VC的初始位置,能夠用來作動畫的計算。
  • -(CGRect)finalFrameForViewController:(UIViewController *)vc; 與上面的方法對應,獲得切換結束時某個VC應在的frame。
  • -(void)completeTransition:(BOOL)didComplete; 向這個context報告切換已經完成。

@protocol UIViewControllerAnimatedTransitioning

這個接口負責切換的具體內容,也即「切換中應該發生什麼」。開發者在作自定義切換效果時大部分代碼會是用來實現這個接口。它只有兩個方法須要咱們實現:

  • -(NSTimeInterval)transitionDuration:(id < UIViewControllerContextTransitioning >)transitionContext; 系統給出一個切換上下文,咱們根據上下文環境返回這個切換所須要的花費時間(通常就返回動畫的時間就行了,SDK會用這個時間來在百分比驅動的切換中進行幀的計算,後面再詳細展開)。

  • -(void)animateTransition:(id < UIViewControllerContextTransitioning >)transitionContext; 在進行切換的時候將調用該方法,咱們對於切換時的UIView的設置和動畫都在這個方法中完成。

@protocol UIViewControllerTransitioningDelegate

這個接口的做用比較簡單單一,在須要VC切換的時候系統會像實現了這個接口的對象詢問是否須要使用自定義的切換效果。這個接口共有四個相似的方法:

  • -(id< UIViewControllerAnimatedTransitioning >)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;

  • -(id< UIViewControllerAnimatedTransitioning >)animationControllerForDismissedController:(UIViewController *)dismissed;

  • -(id< UIViewControllerInteractiveTransitioning >)interactionControllerForPresentation:(id < UIViewControllerAnimatedTransitioning >)animator;

  • -(id< UIViewControllerInteractiveTransitioning >)interactionControllerForDismissal:(id < UIViewControllerAnimatedTransitioning >)animator;

前兩個方法是針對動畫切換的,咱們須要分別在呈現VC和解散VC時,給出一個實現了UIViewControllerAnimatedTransitioning接口的對象(其中包含切換時長和如何切換)。後兩個方法涉及交互式切換,以後再說。

  

模態視圖自定義動畫

 1:創建一個模態視圖控制器

//ModalViewController.h
@class ModalViewController;
@protocol ModalViewControllerDelegate <NSObject>
-(void)dismissViewController:(ModalViewController *)mcv;
@end

@interface ModalViewController : UIViewController
@property(nonatomic,weak)id<ModalViewControllerDelegate> delegate;
@end

//ModalViewController.m
@interface ModalViewController ()

@end

@implementation ModalViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.view setBackgroundColor:[UIColor greenColor]];
    // Do any additional setup after loading the view.
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self action:@selector(dismissViewController) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"dismiss" forState:UIControlStateNormal];
    [button setFrame:CGRectMake(0, 0, 130, 200)];
    [self.view addSubview:button];
}

-(void)dismissViewController {
    [self.delegate dismissViewController:self];
}
@end

 

2:創建主視圖控制器,實現ModalViewControllerDelegate協議

//ViewController.h
@interface ViewController : UIViewController<ModalViewControllerDelegate>

@end

//ViewController.m
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self action:@selector(pushViewController) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"push" forState:UIControlStateNormal];
    [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [button setFrame:CGRectMake(0, 0, 130, 200)];
    [self.view addSubview:button];
}


-(void)pushViewController {
    ModalViewController *controller = [[ModalViewController alloc] init];
    controller.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
    controller.delegate = self;

    [self presentViewController:controller animated:YES completion:nil];
}

-(void)dismissViewController:(ModalViewController *)mcv {
    [self dismissViewControllerAnimated:YES completion:nil];
}

@end

 

上面的代碼實現了模態視圖的切換,經過 controller.modalTransitionStyle = UIModalTransitionStyleCoverVertical還能夠設置切換的動畫效果,ios內置了幾種切換效果供開發者使用,可是咱們如今須要自定義動畫效果

3:新建一個類實現UIViewControllerAnimatedTransitioning協議,這個類就是咱們的動畫切換類,ios7實現了動畫切換類與試圖控制器類的解耦,編寫一個動畫切換類能夠反覆重用

//ModalTransitionAnimation.h
@interface ModalTransitionAnimation : NSObject<UIViewControllerAnimatedTransitioning>
@end

//ModalTransitionAnimation.m
@implementation ModalTransitionAnimation

//動畫持續時間,單位是秒
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    return 1;
}

//動畫效果
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    //經過鍵值UITransitionContextToViewControllerKey獲取須要呈現的視圖控制器toVC
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    //獲得toVC徹底呈現後的frame
    CGRect finalFrame = [transitionContext finalFrameForViewController:toVC];
    
    
    if ([toVC isKindOfClass:[ModalViewController class]]) {
        //須要呈現的視圖是模態視圖,此時將模態視圖的frame放到屏幕空間下方,這樣才能實現從下方彈出的效果
        toVC.view.frame = CGRectOffset(finalFrame, 0, [UIScreen mainScreen].bounds.size.height);
    } else {
        //須要呈現的視圖是主視圖,此時將主視圖的frame放在屏幕空間上方,這樣才能實現從上方放下的效果
        toVC.view.frame = CGRectOffset(finalFrame, 0, -[UIScreen mainScreen].bounds.size.height);
    }
    
    //切換在containerView中完成,須要將toVC.view加到containerView中
    UIView *containerView = [transitionContext containerView];
    [containerView addSubview:toVC.view];
    
    
    //開始動畫,這裏使用了UIKit提供的彈簧效果動畫,usingSpringWithDamping越接近1彈性效果越不明顯,此API在IOS7以後才能使用
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                          delay:0
         usingSpringWithDamping:0.6
          initialSpringVelocity:0
                        options:UIViewAnimationOptionCurveEaseIn
                     animations:^{
                         toVC.view.frame = finalFrame;
                     } completion:^(BOOL finished) {
                         //通知系統動畫切換完成
                         [transitionContext completeTransition:YES];
                     }];
}

@end

 

上面的代碼實現了從屏幕下方彈性彈出ModalViewController以及將ModalViewController彈回屏幕下方的動畫效果,上面代碼[toVC isKindOfClass:[ModalViewController class]]將動畫切換類與視圖控制器耦合了起來,實際開發中不是一種好的方式,此處僅僅爲了演示

4:從新配置主視圖控制器,使用咱們自定義的動畫

//ViewController.h
//若是須要使用自定義動畫,視圖須要實現UIViewControllerTransitioningDelegate協議
@interface ViewController : UIViewController<ModalViewControllerDelegate,UIViewControllerTransitioningDelegate>

@end

//ViewController.m
@interface ViewController ()
@property(nonatomic,strong)ModalTransitionAnimation *animation;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    _animation = [[ModalTransitionAnimation alloc] init];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self action:@selector(pushViewController) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"push" forState:UIControlStateNormal];
    [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [button setFrame:CGRectMake(0, 0, 130, 200)];
    [self.view addSubview:button];
}


-(void)pushViewController {
    ModalViewController *controller = [[ModalViewController alloc] init];
    controller.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
    controller.delegate = self;
    controller.transitioningDelegate = self;    
    
    [self presentViewController:controller animated:YES completion:nil];
   
}

-(void)dismissViewController:(ModalViewController *)mcv {
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    return _animation;
}

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
    return _animation;

}

@end

 

咱們設置了模態視圖控制器的transitioningDelegate爲self,當present和dismiss模態視圖時系統會像實現了這個接口的對象詢問是否須要使用自定義的切換效果

這個接口共有四個相似的方法:

  • -(id< UIViewControllerAnimatedTransitioning >)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;

  • -(id< UIViewControllerAnimatedTransitioning >)animationControllerForDismissedController:(UIViewController *)dismissed;

  • -(id< UIViewControllerInteractiveTransitioning >)interactionControllerForPresentation:(id < UIViewControllerAnimatedTransitioning >)animator;

  • -(id< UIViewControllerInteractiveTransitioning >)interactionControllerForDismissal:(id < UIViewControllerAnimatedTransitioning >)animator;

咱們實現了前兩個方法用來設置模態視圖出現和消失的動畫,後兩個方法用來處理交互式動畫,後面會提到使用方法。

5:運行代碼,能夠看到模態視圖自定義的動畫效果

 

UINavigationController自定義動畫切換

除了使用模態視圖,導航欄控制器也是使用最多最多見的視圖切換方式,咱們也能夠自定義導航欄控制器的動畫

1:經過storyboard創建一個導航欄控制器

 

此時運行程序咱們就有兩個視圖控制器能夠切換(storyboard真心強大,特別ios8引入sizeclass後強烈建議使用storyboard) 

 

2:創建咱們的動畫切換類

//NavigationTransitionAnimation.h
@interface NavigationTransitionAnimation : NSObject<UIViewControllerAnimatedTransitioning>

@end

//NavigationTransitionAnimation.m
@implementation NavigationTransitionAnimation

//動畫持續時間0.7秒
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    return 0.7;
}


- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    //經過鍵值UITransitionContextToViewControllerKey得到須要呈現的試圖控制器
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    //經過鍵值UITransitionContextFromViewControllerKey得到須要退出的試圖控制器
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    [[transitionContext containerView] addSubview:toVC.view];
    
    //設置須要呈現的試圖控制器透明
    [toVC.view setAlpha:0];
    //設置須要呈現的試圖控制器位於左側屏幕外,且大小爲0.1倍,這樣纔有從左側推入屏幕,且逐漸變大的動畫效果
    toVC.view.transform = CGAffineTransformScale(CGAffineTransformMakeTranslation(-[UIScreen mainScreen].bounds.size.width, 0), 0.1, 0.1);
    
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        //將須要退出的試圖控制器移出右側屏幕外,且大小爲原來的0.1倍
        fromVC.view.transform = CGAffineTransformScale(CGAffineTransformMakeTranslation([UIScreen mainScreen].bounds.size.width, 0), 0.1, 0.1);
        fromVC.view.alpha = 0;

        toVC.view.transform = CGAffineTransformIdentity;
        toVC.view.alpha = 1;
 
    } completion:^(BOOL finished) {
        //動畫結束後屬性設爲初始值
        fromVC.view.transform = CGAffineTransformIdentity;
        fromVC.view.alpha = 1;

        //通知系統動畫切換成功
        [transitionContext completeTransition:YES];
    }];
}
@end

 

3:爲導航欄控制器添加動畫效果,以前爲模態視圖設置自定義動畫時有個協議UIViewControllerTransitioningDelegate,只有設置了協議且實現了協議方法,那麼模態視圖切換時會使用設置的自定義動畫,在導航欄控制器中一樣有一個協議定義了一系列方法用來切換視圖控制器時詢問是否使用自定義方法,那就是UINavigationControllerDelegate

//NavigationControllerDelegate.h
@interface NavigationControllerDelegate : NSObject<UINavigationControllerDelegate>

@end

//NavigationControllerDelegate.m
@interface NavigationControllerDelegate()

@property (weak, nonatomic) IBOutlet UINavigationController *navigationController;
@property(nonatomic,retain)NavigationTransitionAnimation *animation;
@end

@implementation NavigationControllerDelegate


-(void)awakeFromNib {
    self.animation = [[NavigationTransitionAnimation alloc] init];
}

- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC {
    if (operation == UINavigationControllerOperationPop) {
        return _animation;
    }
    
    return nil;
}

@end

 上面代碼只在pop時使用了自定義動畫,固然也能夠在push和pop時均使用自定義動畫,而且能夠爲兩種操做使用不一樣的自定義動畫

UINavigationControllerDelegate中有兩個方法與視圖控制器切換動畫相關

- (id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController

- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC

其中第一個方法用於交互式動畫

 

4:連結@property (weak, nonatomic) IBOutlet UINavigationController *navigationController和storyboard中的導航欄控制器,並設置導航欄控制器的delegate

在storyboard中設置delegate的步驟以下:

首先選擇object

而後將object拖到navigationcontroller上

而後設置class

最後與navigationcontroller的delegate連結

 

5:運行代碼,能夠看到導航欄控制器自定義的動畫效果

 

UITabBarController自定義動畫切換

 接下來咱們來看第三種常見的試圖控制器切換方法:UITabBarController

 1:經過storyboard創建一個UITabBarController

 

 

 

2:創建咱們的動畫切換類

//TabbarTransitionAnimation.h
@interface TabbarTransitionAnimation : NSObject<UIViewControllerAnimatedTransitioning>

@end

//TabbarTransitionAnimation.m
#define PERSPECTIVE -1.0/200
@implementation TabbarTransitionAnimation

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    return 0.7;
}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    
    CATransform3D viewFromTransform = CATransform3DMakeRotation(M_PI/2, 0, 1, 0);
    CATransform3D viewToTransform = CATransform3DMakeRotation(-M_PI/2, 0, 1, 0);
    viewFromTransform.m34 = PERSPECTIVE;
    viewToTransform.m34 = PERSPECTIVE;
    
    
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIView *container = [transitionContext containerView];
    
    [toVC.view.layer setAnchorPoint:CGPointMake(0, 0.5)];
    [fromVC.view.layer setAnchorPoint:CGPointMake(1, 0.5)];
    
    toVC.view.layer.transform = viewToTransform;

    [container addSubview:toVC.view];
    container.transform = CGAffineTransformMakeTranslation(container.frame.size.width/2.0,0);
    

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        
        
        fromVC.view.layer.transform = viewFromTransform;
        toVC.view.layer.transform = CATransform3DIdentity;
        [container setTransform:CGAffineTransformMakeTranslation(-container.frame.size.width/2.0, 0)];
        
        
    } completion:^(BOOL finished) {

        
        fromVC.view.layer.transform = CATransform3DIdentity;
        toVC.view.layer.transform = CATransform3DIdentity;
        [fromVC.view.layer setAnchorPoint:CGPointMake(0.5f, 0.5f)];
        [toVC.view.layer setAnchorPoint:CGPointMake(0.5f, 0.5f)];
        [container setTransform:CGAffineTransformIdentity];
        
        [transitionContext completeTransition:YES];
    }];
    
    
}
@end

 

以上代碼實現了矩形切換的動畫效果

3:爲UITabBarController添加動畫效果,相關的協議是UITabBarControllerDelegate

//TabbarControllerDelegate.h
@interface TabbarControllerDelegate : NSObject<UITabBarControllerDelegate>

@end

//TabbarControllerDelegate.m
@interface TabbarControllerDelegate()
@property (weak, nonatomic) IBOutlet UITabBarController *tabbarController;
@property(nonatomic,strong)TabbarTransitionAnimation *animation;
@end

@implementation TabbarControllerDelegate

- (void)awakeFromNib {
    _animation = [[TabbarTransitionAnimation alloc] init];
}

- (id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
            animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                              toViewController:(UIViewController *)toVC  NS_AVAILABLE_IOS(7_0) {
    
    
    return _animation;
}
@end

4:連結@property (weak, nonatomic) IBOutletUITabBarController *tabbarController和storyboard中的UITabBarController,並設置UITabBarController的delegate(方式和導航欄控制器一致)

5:運行代碼,能夠看到UITabBarController自定義的動畫效果

 

模態視圖交互式動畫切換

前面全部的切換都是當點擊完一個按鈕後就馬上執行,可是有時候咱們但願某些切換操做能夠進行到一半取消,好比咱們爲前一個模態視圖切換提供手勢支持,隨着手勢向下拉,模態視圖慢慢退出屏幕底部,但當咱們拉到一半取消或者向上拉,模態視圖就會回到以前的狀態,這就是所謂的交互式切換

咱們在前一個例子的基礎上添加交互式動畫切換

1:動畫效果類(徹底可使用以前的動畫效果類,這裏爲了說明自定義交互式動畫的流程因此從新寫一個自定義動畫)

//ModalMoveTransitionAnimation.h
@interface ModalMoveTransitionAnimation : NSObject<UIViewControllerAnimatedTransitioning>

@end

//ModalMoveTransitionAnimation.m
@implementation ModalMoveTransitionAnimation


- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    return 1;
}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    CGRect screenBounds = [[UIScreen mainScreen] bounds];
    CGRect initFrame = [transitionContext initialFrameForViewController:fromVC];
    CGRect finalFrame = CGRectOffset(initFrame, 0, screenBounds.size.height);
    
    UIView *containerView = [transitionContext containerView];
    [containerView addSubview:toVC.view];
    [containerView sendSubviewToBack:toVC.view];
    
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        fromVC.view.frame = finalFrame;
    } completion:^(BOOL finished) {
        
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];
    
}
@end

 

 上面代碼中值得注意的是最後[transitionContext completeTransition:![transitionContext transitionWasCancelled]];由於在交互式切換中切換可能會取消,因此這裏使用[transitionContext transitionWasCancelled]判斷切換是否成功

2:添加手勢

首先咱們須要在剛纔的知識基礎上補充一些東西:

首先是UIViewControllerContextTransitioning,剛纔提到這個是系統提供的VC切換上下文,若是您深刻看了它的頭文件描述的話,應該會發現其中有三個關於InteractiveTransition的方法,正是用來處理交互式切換的。可是在初級的實際使用中咱們其實能夠不太理會它們,而是使用iOS 7 SDK已經給咱們準備好的一個現成轉爲交互式切換而新加的類:UIPercentDrivenInteractiveTransition。

UIPercentDrivenInteractiveTransition是什麼

這是一個實現了UIViewControllerInteractiveTransitioning接口的類,爲咱們預先實現和提供了一系列便利的方法,能夠用一個百分比來控制交互式切換的過程。通常來講咱們更多地會使用某些手勢來完成交互式的轉移(固然用的高級的話用其餘的輸入..好比聲音,iBeacon距離或者甚至面部微笑來作輸入驅動也無不可,畢竟想象無極限嘛..),這樣使用這個類(通常是其子類)的話就會很是方便。咱們在手勢識別中只須要告訴這個類的實例當前的狀態百分好比何,系統便根據這個百分比和咱們以前設定的遷移方式爲咱們計算當前應該的UI渲染,十分方便。具體的幾個重要方法:

  • -(void)updateInteractiveTransition:(CGFloat)percentComplete 更新百分比,通常經過手勢識別的長度之類的來計算一個值,而後進行更新。以後的例子裏會看到詳細的用法
  • -(void)cancelInteractiveTransition 報告交互取消,返回切換前的狀態
  • –(void)finishInteractiveTransition 報告交互完成,更新到切換後的狀態

@protocol UIViewControllerInteractiveTransitioning

就如上面提到的,UIPercentDrivenInteractiveTransition只是實現了這個接口的一個類。爲了實現交互式切換的功能,咱們須要實現這個接口。由於大部分時候咱們其實不須要本身來實現這個接口,所以在這篇入門中就不展開說明了,有興趣的童鞋能夠自行鑽研。

還有就是上面提到過的UIViewControllerTransitioningDelegate中的返回Interactive實現對象的方法,咱們一樣會在交互式切換中用到它們。

 

UIPercentDrivenInteractiveTransition能夠直接拿來使用,也能夠繼承它,這裏咱們繼承它

//ModalInterActiveTransitionAnimation.h
@interface ModalInterActiveTransitionAnimation : UIPercentDrivenInteractiveTransition

@property(nonatomic,assign)BOOL interacting;
- (void)wireToViewController:(UIViewController*)viewController;


@end

//ModalInterActiveTransitionAnimation.m
@interface ModalInterActiveTransitionAnimation()

@property (nonatomic, strong) UIViewController *presentingVC;
@property (nonatomic, assign) BOOL shouldComplete;

@end

@implementation ModalInterActiveTransitionAnimation

- (void)wireToViewController:(UIViewController*)viewController{
    _presentingVC = viewController;
    
    //添加手勢
    UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
    [viewController.view addGestureRecognizer:gesture];
}

-(CGFloat)completionSpeed
{
    return 1 - self.percentComplete;
}

-(void)handleGesture:(UIPanGestureRecognizer *)gestureRecognizer {
    CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view.superview];
    switch (gestureRecognizer.state) {
        case UIGestureRecognizerStateBegan:{
            _interacting = YES;
            [self.presentingVC dismissViewControllerAnimated:YES completion:nil];
            break;
        }
        
        case UIGestureRecognizerStateChanged: {
            CGFloat fraction = translation.y / 400.0;
            fraction = fminf(fmaxf(fraction, 0.0), 1.0);
            _shouldComplete = (fraction > 0.5);
            
            [self updateInteractiveTransition:fraction];
            break;
        }
            
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled: {
            _interacting = NO;
            if (!_shouldComplete || gestureRecognizer.state == UIGestureRecognizerStateCancelled || [gestureRecognizer velocityInView:gestureRecognizer.view].y < 0 ) {
                [self cancelInteractiveTransition];
            } else {
                [self finishInteractiveTransition];
            }
            
            break;
        }
        default:
            break;
    }
}

@end

 

上面代碼中的interacting用來判斷當前是否處於交互式視圖切換過程當中,只有處在這個過程當中咱們才須要在

- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator

使用動畫

 

3:添加交互式動畫切換

//ViewController.m
@interface ViewController ()

@property(nonatomic,strong)ModalTransitionAnimation *animation;
@property(nonatomic,strong)ModalInterActiveTransitionAnimation *interActive;
@property(nonatomic,strong)ModalMoveTransitionAnimation *interActiveAnimation;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    _animation = [[ModalTransitionAnimation alloc] init];
    _interActive = [[ModalInterActiveTransitionAnimation alloc] init];
    _interActiveAnimation = [[ModalMoveTransitionAnimation alloc] init];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self action:@selector(pushViewController) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"push" forState:UIControlStateNormal];
    [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [button setFrame:CGRectMake(0, 0, 130, 200)];
    [self.view addSubview:button];
}


-(void)pushViewController {
    ModalViewController *controller = [[ModalViewController alloc] init];
    controller.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
    controller.delegate = self;
    controller.transitioningDelegate = self;
    [_interActive wireToViewController:controller];
    
    
    [self presentViewController:controller animated:YES completion:nil];
   
}

-(void)dismissViewController:(ModalViewController *)mcv {
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    return _animation;
}

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
    return self.interActive.interacting ? _interActiveAnimation : _animation;

}

- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator {
    return self.interActive.interacting ? self.interActive : nil;
}

@end

 

回顧上面的流程,咱們爲view添加手勢,而後在手勢中根據狀況調用UIPercentDrivenInteractiveTransition的三個方法(updateInteractiveTransition,cancelInteractiveTransition,finishInteractiveTransition),最後在interactionControllerForDismissal中返回咱們定義的動畫,這樣就能夠實現交互式動畫切換了

 

UINavigationController交互式動畫切換

 與上面的流程類似

1:添加手勢,在手勢中調用UIPercentDrivenInteractiveTransition的三個方法

2:在相應的代理方法中返回咱們定義的動畫

這裏咱們不用繼承UIPercentDrivenInteractiveTransition,而是直接使用它,同時動畫也直接使用以前在導航欄控制器切換中定義好的動畫,不過代碼須要稍加修改

//NavigationTransitionAnimation.m
@implementation NavigationTransitionAnimation

//動畫持續時間0.7秒
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    return 0.7;
}


- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    //經過鍵值UITransitionContextToViewControllerKey得到須要呈現的試圖控制器
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    //經過鍵值UITransitionContextFromViewControllerKey得到須要退出的試圖控制器
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    [[transitionContext containerView] addSubview:toVC.view];
    
    //設置須要呈現的試圖控制器透明
    [toVC.view setAlpha:0];
    //設置須要呈現的試圖控制器位於左側屏幕外,且大小爲0.1倍,這樣纔有從左側推入屏幕,且逐漸變大的動畫效果
    toVC.view.transform = CGAffineTransformScale(CGAffineTransformMakeTranslation(-[UIScreen mainScreen].bounds.size.width, 0), 0.1, 0.1);
    
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        //將須要退出的試圖控制器移出右側屏幕外,且大小爲原來的0.1倍
        fromVC.view.transform = CGAffineTransformScale(CGAffineTransformMakeTranslation([UIScreen mainScreen].bounds.size.width, 0), 0.1, 0.1);
        fromVC.view.alpha = 0;

        toVC.view.transform = CGAffineTransformIdentity;
        toVC.view.alpha = 1;
 
    } completion:^(BOOL finished) {
        //動畫結束後屬性設爲初始值
        fromVC.view.transform = CGAffineTransformIdentity;
        fromVC.view.alpha = 1;
        
        toVC.view.transform = CGAffineTransformIdentity;
        toVC.view.alpha = 1;
        
        
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];
}
@end

咱們修改了最後幾行代碼,讓它適應交互式切換

 

添加咱們的手勢以及自定義交互動畫

@interface NavigationControllerDelegate()

@property (weak, nonatomic) IBOutlet UINavigationController *navigationController;
@property(nonatomic,retain)NavigationTransitionAnimation *animation;
@property (strong, nonatomic) UIPercentDrivenInteractiveTransition* interactionController;
@property (nonatomic,assign)BOOL interActiving;
@end

@implementation NavigationControllerDelegate


-(void)awakeFromNib {
    self.animation = [[NavigationTransitionAnimation alloc] init];
    
    self.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];
    
    [self.navigationController.view addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)]];
}

-(void)handleGesture:(UIPanGestureRecognizer *)gesture {
    UIView* view = self.navigationController.view;
    CGPoint location = [gesture locationInView:gesture.view];
    CGPoint translation = [gesture translationInView:gesture.view];
    
    switch (gesture.state) {
        case UIGestureRecognizerStateBegan: {
            _interActiving = YES;
            if (location.x < CGRectGetMidX(view.bounds) && self.navigationController.viewControllers.count > 1) {
                [self.navigationController popViewControllerAnimated:YES];
            }
            break;
        }
        case UIGestureRecognizerStateChanged: {
            CGFloat fraction = fabs(translation.x / view.bounds.size.width);
            [_interactionController updateInteractiveTransition:fraction];
            break;
        }
        case UIGestureRecognizerStateCancelled:
        case UIGestureRecognizerStateEnded: {
            _interActiving = NO;
            CGFloat fraction = fabs(translation.x / view.bounds.size.width);
            if (fraction < 0.5 || [gesture velocityInView:view].x < 0 || gesture.state == UIGestureRecognizerStateCancelled) {
                [_interactionController cancelInteractiveTransition];
            } else {
                [_interactionController finishInteractiveTransition];
            }
            
            break;
        }
        default:
            break;
    }
}

- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC {
    if (operation == UINavigationControllerOperationPop) {
        return _animation;
    }
    
    return nil;
}


- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
    return self.interActiving ? self.interactionController : nil;
}
@end

 

最後

在ios7以後導航欄控制器自帶了一個交互式切換動畫,咱們只要從屏幕左側向右滑就能回到上一層

咱們能夠自定義這個手勢是否開啓(默認開啓)

self.navigationController.interactivePopGestureRecognizer.enabled = YES;

 

但當咱們定義了leftBarButtonItem後這個手勢每每會失效,解決方法能夠看這篇博客

相關文章
相關標籤/搜索