1. 程式人生 > >【轉】自定義presentviewcontroller和pushviewcontroller轉場動畫

【轉】自定義presentviewcontroller和pushviewcontroller轉場動畫

自定義NavigationController動畫

首先,實現一個非常簡單的UINavigationController轉場,一般會這麼幹

實現FirstViewController,加到Window上(沒用storyboard和xib)

實現FirstViewController上面有個按鈕,點選後push到SecondViewController

貼一下FirstViewController的關鍵程式碼,這很簡單

- (void)viewDidLoad {

    [super viewDidLoad];

    //init First

    self.navigationItem.title = @"First";

    self.view.backgroundColor = [UIColor orangeColor];

    //init Second

    secondViewController = [[SecondViewController alloc] init];

    // Push

    UIButton *pushButton = [UIButton buttonWithType:UIButtonTypeSystem];

    pushButton.frame = CGRectMake(140, 200, 40, 40);

    [pushButton setTitle:@"Push" forState:UIControlStateNormal];

    [pushButton addTarget:self action:@selector(push) forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:pushButton];

}

- (void)push

{

    [self.navigationController pushViewController:secondViewController animated:YES];

}

下面我們要自定義這個push動畫

我們先實現一個push自定義動畫類姑且叫做CustomPushAnimation,它實現UIViewControllerAnimatedTransitioning協議,用來定義一個非互動動畫(就是動畫過程中沒互動)

裡面也用到了UIViewControllerContextTransitioning這個協議,可以理解為轉場動畫的上下文,一個容器。下面是程式碼.h和.m,記住這是一個自定義的push的動畫

#import <Foundation/Foundation.h>

#import <UIKit/UIKit.h>

@interface CustomPushAnimation : NSObject<UIViewControllerAnimatedTransitioning>

@end

#import "CustomPushAnimation.h"

@implementation CustomPushAnimation

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext

{

    return 3.0;

}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext

{

    //目的ViewController

    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

    //起始ViewController

    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

    

    //新增toView到上下文

    [[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];

    

    //自定義動畫

    toViewController.view.transform = CGAffineTransformMakeTranslation(320, 568);

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{

        fromViewController.view.transform = CGAffineTransformMakeTranslation(-320, -568);

        toViewController.view.transform = CGAffineTransformIdentity;

    } completion:^(BOOL finished) {

        fromViewController.view.transform = CGAffineTransformIdentity;

        // 宣告過渡結束時呼叫 completeTransition: 這個方法

        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];

    }];

}

@end

下面在FirstViewController裡面初始化CustomPush動畫,並給FirstViewController新增UINavigationController代理
    //FirstViewController viewDidLoad
    self.navigationController.delegate = self;

    //init CustomPush
    customPush = [[CustomPushAnimation alloc] init];

為什麼要新增代理呢?怎麼用這個CustomPush動畫呢?

答案就是我們要用UINavigationControllerDelegate的

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

這個方法來選中Push轉場的時候用我們自己定義的CustomPush動畫。

#pragma mark - UINavigationControllerDelegate iOS7<span class="s1">非互動自定義</span>Navigation<span class="s1">轉場</span>
// 動畫特效

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

{

    /**

     *  typedef NS_ENUM(NSInteger, UINavigationControllerOperation) {

     *     UINavigationControllerOperationNone,

     *     UINavigationControllerOperationPush,

     *     UINavigationControllerOperationPop,

     *  };

     */
    //push的時候用我們自己定義的customPush
    if (operation == UINavigationControllerOperationPush) {

        return customPush;

    }else{

        return nil;

    }

}

大功告成,現在就執行一下試試吧,可以發現動畫像我想的一樣,FirstViewController往左上角飄,SecondViewController緊隨其後,這樣完成了轉場,這樣我們就自定義了一個push的轉場動畫!

很有趣吧,下面照葫蘆畫瓢,做一個Pop動畫吧!聰明的你一定知道,只需要在自定義轉場的代理裡面加一個if語句判斷pop就好

// 動畫特效

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

{

    /**

     *  typedef NS_ENUM(NSInteger, UINavigationControllerOperation) {

     *     UINavigationControllerOperationNone,

     *     UINavigationControllerOperationPush,

     *     UINavigationControllerOperationPop,

     *  };

     */

    if (operation == UINavigationControllerOperationPush) {

        return customPush;

    }else if (operation == UINavigationControllerOperationPop) {

        return customPop;

    }else{

        return nil;

    }

}

**********************************************************************************************************************

自定義PresentViewController動畫

NavigationController搞定,Present也依葫蘆畫瓢

工程裡新增第三個ViewController 叫做ThirdViewController,然後在FirstViewController裡面新增下面程式碼

    //init Third

    thirdViewController = [[ThirdViewController alloc] init];



    // Present

    UIButton *presentButton = [UIButton buttonWithType:UIButtonTypeSystem];

    presentButton.frame = CGRectMake(110, 400, 100, 40);

    [presentButton setTitle:@"Present" forState:UIControlStateNormal];

    [presentButton addTarget:self action:@selector(present) forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:presentButton];

然後ThirdViewController新增一個按鈕用來Dismiss自己,這個就不寫了,然後執行起來實現的功能就很正常,點選Present按鈕就會自下而上的一個動畫推入新的ViewController,這很正常,然後現在來實現高階動畫

首先,和Navigation類似,在FirstViewController我們要做一些準備工作

修改FirstViewController的協議

@interface FirstViewController ()<UINavigationControllerDelegate,UIViewControllerTransitioningDelegate>

然後和NavigationController有區別的是給自己的“下家兒”,也就是要轉去的ViewController設定代理
thirdViewController.transitioningDelegate = self;

接下來就要在present時候和dismiss兩個協議方法設定自定義動畫,下面兩個方法來於UIViewControllerTransitioningDelegate
#pragma mark - UIViewControllerTransitioningDelegate

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

{

    customPresent.animationType = AnimationTypePresent;

    return customPresent;

}

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

{

    customPresent.animationType = AnimationTypeDismiss;

    return customPresent;

}

那麼,customPresent.animationType = AnimationTypePresent;這是什麼東西呢?不要著急,這個就是自定義的Present動畫效果,下面就講他的實現,但是目前已經可以看到程式碼寫起來和NavigationController的自定義動畫非常像,對仗工整,都是協議,設定代理,呼叫代理時候使用自定義的動畫。

下面定義真正的自定義動畫CustomPresentAnimation.h

typedef enum {

    AnimationTypePresent,

    AnimationTypeDismiss

} AnimationType;

#import <Foundation/Foundation.h>

#import <UIKit/UIKit.h>

@interface CustomPresentAnimation : NSObject <UIViewControllerAnimatedTransitioning>

@property (nonatomic, assign) AnimationType animationType;

@end

#import "CustomPresentAnimation.h"

@implementation CustomPresentAnimation

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext

{

    return 1.3;

}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {

    

    UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

    UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

    

    UIView * toView = toViewController.view;

    UIView * fromView = fromViewController.view;

    

    if (self.animationType == AnimationTypePresent) {

        

        //snapshot方法是很高效的截圖

        

        //First放下面

        UIView * snap = [fromView snapshotViewAfterScreenUpdates:YES];

        [transitionContext.containerView addSubview:snap];

        //Third放上面

        UIView * snap2 = [toView snapshotViewAfterScreenUpdates:YES];

        [transitionContext.containerView addSubview:snap2];

        

        snap2.transform = CGAffineTransformMakeTranslation(-320, 0);

        

        //進行動畫

        [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{

            snap2.transform = CGAffineTransformIdentity;

        } completion:^(BOOL finished) {

            //刪掉截圖

            [snap removeFromSuperview];

            [snap2 removeFromSuperview];

            //新增檢視

            [[transitionContext containerView] addSubview:toView];

            //結束Transition

            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];

        }];

        

        

    } else {

        

        //First 放下面

        UIView * snap = [toView snapshotViewAfterScreenUpdates:YES];

        [transitionContext.containerView addSubview:snap];

        

        //Third 放上面

        UIView * snap2 = [fromView snapshotViewAfterScreenUpdates:YES];

        [transitionContext.containerView addSubview:snap2];

        

        //進行動畫

        [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{

            snap2.transform = CGAffineTransformMakeTranslation(-320, 0);

        } completion:^(BOOL finished) {

            //刪掉截圖

            [snap removeFromSuperview];

            [snap2 removeFromSuperview];

            //新增檢視

            [[transitionContext containerView] addSubview:toView];

            //結束Transition

            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];

        }];

    }

}

@end

呼叫的時候就和上面程式碼所說一樣,在UIViewControllerTransitioningDelegate代理裡完成,那麼自定義的Present轉場就是這樣了。執行一下,會發現很調皮的ThirdView在Present轉場的時候從左面彈性的進入螢幕。

互動式的動畫

想想iOS7的UINavigationController自帶的動畫,可以用手指慢慢往左滑動來pop出一個頁面,動畫是隨著手指的移動互動式呈現的,這個就很有趣了,那麼除了系統幫我們實現的,我們自己也可以自定義互動式動畫了。

下面就來給我們的App新增一個Pan手勢,給之前實現的Present動畫增加一個互動式功能。

FirstViewController的viewdidload新增pan手勢

    UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didClickPanGestureRecognizer:)];

    [self.navigationController.view addGestureRecognizer:panRecognizer];

然後執行這個方法,在手勢執行的時候重新整理互動狀態,核心方法是updateInteractiveTransition
#pragma mark - 手勢互動的主要實現--->UIPercentDrivenInteractiveTransition

- (void)didClickPanGestureRecognizer:(UIPanGestureRecognizer*)recognizer

{

    UIView* view = self.view;

    if (recognizer.state == UIGestureRecognizerStateBegan) {

        interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];

        [self presentViewController:thirdViewController animated:YES completion:nil];

    } else if (recognizer.state == UIGestureRecognizerStateChanged) {

        CGPoint translation = [recognizer translationInView:view];

        CGFloat distance = fabs(translation.x / CGRectGetWidth(view.bounds));

        [interactionController updateInteractiveTransition:distance];

    } else if (recognizer.state == UIGestureRecognizerStateEnded) {

        CGPoint translation = [recognizer translationInView:view];

        CGFloat distance = fabs(translation.x / CGRectGetWidth(view.bounds));

        if (distance > 0.5) {

            [interactionController finishInteractiveTransition];

        } else {

            [interactionController cancelInteractiveTransition];

        }

        interactionController = nil;

    }

}

這裡需要提一下,初始化下面的程式碼,用於互動式轉場,

//通過 UIViewControllerInteractiveTransitioning 協議進行互動轉場。

UIPercentDrivenInteractiveTransition* interactionController;

然後根據UIViewControllerTransitioningDelegate這個協議新增兩個方法,來說明我們使用互動式轉場

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

{

    return interactionController;

}

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

{

    return nil;

}