自定義UINavigationController push和pop動畫無標題文章

前言

首先, push/pop 動效在iOS7.0以后系統(tǒng)就已經提供了相關的代理方法, 在代理方法中我們可以自定義切換動畫.
自定義push,pop動畫是由UINavigationController的代理方法中提供的.
用于實現(xiàn)自定義動畫的代理(UINavigationControllerDelegate)方法如下:

- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                                   interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS(7_0);

- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                            animationControllerForOperation:(UINavigationControllerOperation)operation
                                                         fromViewController:(UIViewController *)fromVC
                                                           toViewController:(UIViewController *)toVC  NS_AVAILABLE_IOS(7_0);

1. 實現(xiàn)自定義push,pop動畫

一般的自定義動畫通過navigationController:animationControllerForOperation:fromViewController:toViewController:這個代理方法實現(xiàn), 這個代理方法返回id<UIViewControllerAnimatedTransitioning>類型的對象, 也就是說, 只需要定義好實現(xiàn)UIViewControllerAnimatedTransitioning協(xié)議的一個類即可.

1. UIViewControllerAnimatedTransitioning協(xié)議

UIViewControllerAnimatedTransitioning協(xié)議定義如下所示, 不解釋:

@protocol UIViewControllerAnimatedTransitioning <NSObject>

// This is used for percent driven interactive transitions, as well as for container controllers that have companion animations that might need to
// synchronize with the main animation. 
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
// This method can only  be a nop if the transition is interactive and not a percentDriven interactive transition.
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;

@optional

// This is a convenience and if implemented will be invoked by the system when the transition context's completeTransition: method is invoked.
- (void)animationEnded:(BOOL) transitionCompleted;

@end

代碼中實現(xiàn)這個協(xié)議時候思路是這樣的, 整體上是繼承的結構, 因為動畫具體的動效效果可能有n種, 但是基本方法和判斷都是相同的. 父類遵循協(xié)議并實現(xiàn)如上的幾個代理, 父類實現(xiàn)幾個類方法來快速創(chuàng)建對象, 并提供其它初始化方法; 父類完成基本的邏輯判斷和方法調用, 子類繼承父類, 具體實現(xiàn)動效效果. 由于Objective-C中沒有抽象方法, 例子在父類中聲明方法, 空實現(xiàn), 子類重寫父類相關的方法. 父類如下所示:

WHBaseAnimationTransitioning.h

#import <UIKit/UIKit.h>
#import "UIViewController+WHAnimationTransitioningSnapshot.h"

@interface WHBaseAnimationTransitioning : NSObject <UIViewControllerAnimatedTransitioning>

@property (nonatomic, strong, readwrite) UIPercentDrivenInteractiveTransition *interactivePopTransition;

/// 創(chuàng)建動畫效果的實例對象并設置動畫類型, push or pop
+ (instancetype)transitionWithType:(UINavigationControllerOperation)transitionType;

/// 創(chuàng)建動畫效果的實例對象并設置動畫類型和間隔時間
+ (instancetype)transitionWithType:(UINavigationControllerOperation)transitionType duration:(NSTimeInterval)duration;

/// 創(chuàng)建動畫效果實例對象并設置動畫類型/間隔時間/可交互屬性
+ (instancetype)transitionWithType:(UINavigationControllerOperation)transitionType duration:(NSTimeInterval)duration interactivePopTransition:(UIPercentDrivenInteractiveTransition *)interactivePopTransition;

- (instancetype)initWithType:(UINavigationControllerOperation)transitionType duration:(NSTimeInterval)duration;

#pragma mark - 真正實現(xiàn) push, pop 動畫的方法, 具體實現(xiàn)交給子類
- (void)push:(id<UIViewControllerContextTransitioning>)transitionContext;
- (void)pop:(id<UIViewControllerContextTransitioning>)transitionContext;

- (void)pushEnded;
- (void)popEnded;

@end

WHBaseAnimationTransitioning.m

#import "WHBaseAnimationTransitioning.h"

/// 默認動畫執(zhí)行時間間隔
const static NSTimeInterval WHAnimationTransitioningDuration = 0.6;

@interface WHBaseAnimationTransitioning ()

@property (nonatomic, assign) NSTimeInterval duration;
@property (nonatomic, assign) UINavigationControllerOperation transitionType;

@end

@implementation WHBaseAnimationTransitioning

- (instancetype)init {

    if (self = [self initWithType:UINavigationControllerOperationPush duration:WHAnimationTransitioningDuration]) {
    }
    return self;
}

// 主要的構造方法
- (instancetype)initWithType:(UINavigationControllerOperation)transitionType duration:(NSTimeInterval)duration {

    if (self = [super init]) {
        self.duration = duration;
        self.transitionType = transitionType;
    }
    return self;
}

+ (instancetype)transitionWithType:(UINavigationControllerOperation)transitionType {

    return [self transitionWithType:transitionType duration:WHAnimationTransitioningDuration];
}

+ (instancetype)transitionWithType:(UINavigationControllerOperation)transitionType
                          duration:(NSTimeInterval)duration {

    return [self transitionWithType:transitionType duration:duration interactivePopTransition:nil];
}

+ (instancetype)transitionWithType:(UINavigationControllerOperation)transitionType duration:(NSTimeInterval)duration interactivePopTransition:(UIPercentDrivenInteractiveTransition *)interactivePopTransition {

    WHBaseAnimationTransitioning *animationTransitioning = [[self alloc] initWithType:transitionType duration:duration];
    animationTransitioning.interactivePopTransition = interactivePopTransition;
    return animationTransitioning;
}

- (void)push:(id<UIViewControllerContextTransitioning>)transitionContext {}
- (void)pop:(id<UIViewControllerContextTransitioning>)transitionContext {}
- (void)pushEnded {}
- (void)popEnded {}

#pragma mark - UIViewControllerAnimatedTransitioning
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {

    return self.duration;
}

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

    if (self.transitionType == UINavigationControllerOperationPush) {
        [self push:transitionContext];
    }
    else if (self.transitionType == UINavigationControllerOperationPop) {
        [self pop:transitionContext];
    }
}

- (void)animationEnded:(BOOL) transitionCompleted {

    if (!transitionCompleted) return;

    if (self.transitionType == UINavigationControllerOperationPush) {
        [self pushEnded];
    }
    else if (self.transitionType == UINavigationControllerOperationPop) {
        [self popEnded];
    }
}

@end

2. 具體實現(xiàn)動畫

動畫效果如下:

代碼如下:

#import "WHBaseAnimationTransitioning.h"

@interface WHBackPriorViewAnimation : WHBaseAnimationTransitioning

@end

@implementation WHBackPriorViewAnimation

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

    UIViewController * fromVc = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController * toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    NSTimeInterval duration = [self transitionDuration:transitionContext];

    CGRect bounds = [[UIScreen mainScreen] bounds];
    fromVc.view.hidden = YES;
    [[transitionContext containerView] addSubview:fromVc.snapshot];
    [[transitionContext containerView] addSubview:toVc.view];
    [[toVc.navigationController.view superview] insertSubview:fromVc.snapshot belowSubview:toVc.navigationController.view];
    toVc.navigationController.view.transform = CGAffineTransformMakeTranslation(CGRectGetWidth(bounds), 0.0);

    [UIView animateWithDuration:duration
                          delay:0
         usingSpringWithDamping:1.0
          initialSpringVelocity:0.0f
                        options:UIViewAnimationOptionCurveLinear
                     animations:^{
                         fromVc.snapshot.alpha = 0.3;
                         fromVc.snapshot.transform = CGAffineTransformMakeScale(0.965, 0.965);
                         toVc.navigationController.view.transform = CGAffineTransformMakeTranslation(0.0, 0.0);
                     }
                     completion:^(BOOL finished) {
                         fromVc.view.hidden = NO;
                         [fromVc.snapshot removeFromSuperview];
                         [toVc.snapshot removeFromSuperview];
                         [transitionContext completeTransition:YES];
                     }];
}

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

    UIViewController * fromVc = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController * toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    NSTimeInterval duration = [self transitionDuration:transitionContext];
    CGRect bounds = [[UIScreen mainScreen] bounds];

    [fromVc.view addSubview:fromVc.snapshot];
    fromVc.navigationController.navigationBar.hidden = YES;
    fromVc.view.transform = CGAffineTransformMakeTranslation(0.0, 0.0);

    toVc.view.hidden = YES;
    toVc.snapshot.alpha = 0.3;
    toVc.snapshot.transform = CGAffineTransformMakeScale(0.965, 0.965);

    [[transitionContext containerView] addSubview:toVc.view];
    [[transitionContext containerView] addSubview:toVc.snapshot];
    [[transitionContext containerView] sendSubviewToBack:toVc.snapshot];

    [UIView animateWithDuration:duration
                          delay:0
         usingSpringWithDamping:1.0
          initialSpringVelocity:0.1f
                        options:UIViewAnimationOptionCurveLinear
                     animations:^{
                         fromVc.view.transform = CGAffineTransformMakeTranslation(CGRectGetWidth(bounds), 0.0);
                         toVc.snapshot.alpha = 1.0;
                         toVc.snapshot.transform = CGAffineTransformIdentity;
                     }
                     completion:^(BOOL finished) {

                         toVc.navigationController.navigationBar.hidden = NO;
                         toVc.view.hidden = NO;

                         [fromVc.snapshot removeFromSuperview];
                         [toVc.snapshot removeFromSuperview];
                         fromVc.snapshot = nil;

                         // Reset toViewController's `snapshot` to nil
                         if (![transitionContext transitionWasCancelled]) {
                             toVc.snapshot = nil;
                         }

                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];
}

@end

到此為止, 已經成功實現(xiàn)自定義push, pop動畫.

2. 實現(xiàn)可交互的pop動畫

交互式的動畫, 這個概念太大, 這里介紹的是使用手勢pop動畫的實現(xiàn)過程.

1. 動畫效果

動畫效果如下所示:

2. 實現(xiàn)過程

手勢pop動畫, 主要是要監(jiān)聽手勢變化, 然后根據手勢變化更新動畫. 我的實現(xiàn)思路是, 在BaseViewController中給控制器添加手勢并監(jiān)聽, 如果是右滑則開始執(zhí)行pop動畫, 具體代碼如下所示:

// 判斷是否是根控制器 并添加手勢
if (self.navigationController != nil && self != self.navigationController.viewControllers.firstObject) {
        UIPanGestureRecognizer *popRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePopRecognizer:)];
        [self.view addGestureRecognizer:popRecognizer];
        popRecognizer.delegate = self;
    }

#pragma mark - UIPanGestureRecognizer handlers
// 實現(xiàn)監(jiān)聽手勢變化的代理方法
- (void)handlePopRecognizer:(UIPanGestureRecognizer *)recognizer {

    CGFloat progress = [recognizer translationInView:self.view].x / CGRectGetWidth(self.view.frame);
    progress = MIN(1.0, MAX(0.0, progress));

    if (recognizer.state == UIGestureRecognizerStateBegan) {

        // Create a interactive transition and pop the view controller
        self.interactivePopTransition = [[UIPercentDrivenInteractiveTransition alloc] init];

        [self.navigationController popViewControllerAnimated:YES];
    } else if (recognizer.state == UIGestureRecognizerStateChanged) {

        // Update the interactive transition's progress
        [self.interactivePopTransition updateInteractiveTransition:progress];
    } else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled) {

        // Finish or cancel the interactive transition
        if (progress > 0.25) {
            [self.interactivePopTransition finishInteractiveTransition];
        } else {
            [self.interactivePopTransition cancelInteractiveTransition];
        }

        self.interactivePopTransition = nil;
    }
}

- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)recognizer {
    return [recognizer velocityInView:self.view].x > 0;
}

BaseViewController中實現(xiàn)以上方法即可, 要想實現(xiàn)側滑功能, 還需要實現(xiàn)NAVi的一個代理方法:navigationController:interactionControllerForAnimationController:, 在這個代理方法中返回遵循UIViewControllerInteractiveTransitioning協(xié)議的對象即可.

- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                                   interactionControllerForAnimationController:(WHBaseAnimationTransitioning *) animationController  {

    return animationController.interactivePopTransition;
}

至此, 手勢實現(xiàn)pop效果已經實現(xiàn)完畢, 如有任何疑問, 歡迎交流.

另有其它幾種動效效果, 詳見github源碼.

如果您覺得本文有用, 歡迎star支持一下 ?? .

源碼地址: https://github.com/hell03W/SwitchControllerAnimation

后記

博文作者:hell03W

博文出處: http://my.oschina.net/whforever

github: https://github.com/hell03W

oschina: http://my.oschina.net/whforever

jianshu: http://www.itdecent.cn/users/ea059360a6f6

本文版權歸作者,歡迎轉載,但須保留此段聲明,并給出原文鏈接,謝謝合作!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容