有关UINavigationController页面之间的跳转动画

最近遇到一个需求,一个导航控制器上有一个按钮点击弹出一个全屏幕的蒙层,然后蒙层上有个按钮点击从屏幕右边push过来一个页面;

先来张草图感受下需求是什么(很直观有木有):

有关UINavigationController页面之间的跳转动画_第1张图片
页面跳转.png

我们可以形象的举个,如果蒙层我们也算做一个控制器的话,有三个控制器,oneVC,twoVC,threeVC,oneVC和threeVC是有导航栏的,twoVC(蒙层)是全屏幕的控制器,需求是点击oneVC上的按钮弹出twoVC(要求动画是弹出),点击twoVC上的按钮push过来threeVC(动画是一般的屏幕右边push过来)

好了,拿到这个需求,这不是很简单吗?刚开始做的时候也没多想,项目里有一种蒙层控制器弹出的方法,是UIViewController的分类方法,直接通过oneVC控制器调用presentInWindow

- (void)presentInWindow
{
    self.view.alpha = 0;
    self.view.backgroundColor = [UIColor clearColor];
    [self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
    UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
    [keyWindow addSubview:self.view];
    [self retain];
    
    NSDictionary *dic = @{@"_view_": self.view};
    [keyWindow addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_view_]|"
                                                                      options:0
                                                                      metrics:0
                                                                        views:dic]];
    [keyWindow addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_view_]|"
                                                                      options:0
                                                                      metrics:0
                                                                        views:dic]];
    
    __block typeof(self) weakSelf = self;
    
    [UIView animateWithDuration:0.3 animations:^{
        weakSelf.view.alpha = 1.0f;
    }];
}

- (void)dismiss
{
    __block typeof(self) weakSelf = self;
    
    [UIView animateWithDuration:0.3 animations:^{
        weakSelf.view.alpha = 0.0f;
    } completion:^(BOOL finished) {
        [weakSelf.view removeFromSuperview];
        [weakSelf release];
    }];
}

然后再点击twoVC上的按钮调用系统的push方法

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

好了 点击完根本没有任何反应,而且断点直接走到了threeVC的 -(void)dealloc方法,突然懵逼了,这才发现调用presentInWindow出来的twoVC并不是导航控制器,再调用push方法,所以跳转不过去;看到有人实现的一种方案,他是这样实现的:
首先调用presentInWindow弹出twoVC,然后点击twoVC上的按钮时,调用了 - (void)dismiss方法关闭twoVC,通过回调的方法从oneVC push到threeVC,但是这个有个问题就是返回的时候twoVC不见了,可能有人想问,我通过回调从 oneVC 跳转到threeVC的时候不关闭twoVC不就保证三个页面都在了么,再仔细看看才发现presentInWindow是把twoVC加到了UIWindow上,这样threeVC push过来的时候层级就在twoVC下面,就被盖着了。

经过尝试,找到了三种解决方案,分享给大家,实现方式都很简单,都是把三个VC作为导航控制器来跳转。

方案一

这种方式借助上面的presentInWindow方法,直接把twoVC包装成UINavigationController,然后去调用presentInWindow,代码如下:

    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:twoVC];
    [nav presentInWindow];

关闭twoVC调用

    [self.navigationController dismiss];

然后再跳转threeVC

    [self.navigationController pushViewController:threeVC animated:NO];

方案二

这种方式是利用系统的CATransition动画来设置弹出效果,废话不多说,上代码:

    CATransition *animation = [CATransition animation];
    [animation setDuration:0.3];
    [animation setType: kCATransitionFade];
    [animation setSubtype: kCATransitionFromTop];
    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
    [self.navigationController pushViewController:twoVC animated:NO];
    [self.navigationController.view.layer addAnimation:animation forKey:nil];

关闭当前页面直接调用

      [self.navigationController popViewControllerAnimated:NO];

这种方式其实就是正常的push,只是给push添加了一个动画,也比较简单

方案三

这种方式相比前两种最为复杂,因为可以自定义,是iOS7新增的;
苹果给我们开发者提供的是都是协议接口,所以我们能够很好的单独提出来写成一个个类,在里面实现我们各种自定义效果
1.先来看看实现UIViewControllerAnimatedTransitioning的自定义动画类
我们定义了一个实体类CustomAnimator:

/**
 *  自定义的动画类
 *  实现协议------>@protocol UIViewControllerAnimatedTransitioning
 *  这个接口负责切换的具体内容,也即“切换中应该发生什么”
 */
//CustomAnimator.h
#import 
typedef enum {
    AnimationTypePresent,
    AnimationTypeDismiss
} AnimationType;

@interface CustomAnimator : NSObject
@property (nonatomic, assign) AnimationType type;
@end

//CustomAnimator.m
@implementation CustomAnimator

// 系统给出一个切换上下文,我们根据上下文环境返回这个切换所需要的花费时间
- (NSTimeInterval)transitionDuration:(id)transitionContext
{
    return 1.0;
}

// 完成容器转场动画的主要方法,我们对于切换时的UIView的设置和动画都在这个方法中完成
- (void)animateTransition:(id)transitionContext
{
    // 可以看做为destination ViewController
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    // 可以看做为source ViewController
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    if (self.type == AnimationTypePresent) {
        //Add 'to' view to the hierarchy with 0.0 scale
        toViewController.view.transform = CGAffineTransformMakeScale(0.95, 0.95);
        toViewController.view.alpha = 0;
        [containerView insertSubview:toViewController.view aboveSubview:fromViewController.view];
        
        //iOS9 兼容   
        [toViewController.view mas_makeConstraints:^(MASConstraintMaker *make) {
            make.edges.mas_equalTo(UIEdgeInsetsZero);
        }];
        
        [UIView animateKeyframesWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:0 animations:^{
            [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.15 animations:^{
                toViewController.view.transform = CGAffineTransformMakeScale(1.0, 1.0);
            }];
            [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:[self transitionDuration:transitionContext] animations:^{
                toViewController.view.alpha = 1;
            }];
        } completion:^(BOOL finished) {
            [transitionContext completeTransition:YES];
        }];
    } else if (self.type == AnimationTypeDismiss) {
        //Add 'to' view to the hierarchy
        [containerView insertSubview:toViewController.view belowSubview:fromViewController.view];
        [UIView animateKeyframesWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:0 animations:^{
            [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:[self transitionDuration:transitionContext] animations:^{
                fromViewController.view.alpha = 0;
            }];
            [UIView addKeyframeWithRelativeStartTime:0.15 relativeDuration:0.15 animations:^{
                fromViewController.view.transform = CGAffineTransformMakeScale(0.95, 0.95);
            }];
        } completion:^(BOOL finished) {
            [transitionContext completeTransition:YES];
        }];
    }
}

PS:从协议中两个方法可以看出,上面两个必须实现的方法需要一个转场上下文参数,这是一个遵从UIViewControllerContextTransitioning 协议的对象。通常情况下,当我们使用系统的类时,系统框架为我们提供的转场代理(Transitioning Delegates),为我们创建了转场上下文对象,并把它传递给动画控制器。

然后在oneVC中需要实现UINavigationControllerDelegate

    //**不要忘了设置代理**
    self.navigationController.delegate = self;

跳转还是正常的push

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

然后在oneVC中实现以下代理方法:

- (id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
   //这里需要判断下fromVC和toVC
    CustomAnimator *animationController = [[CustomAnimator alloc] init];
    switch (operation) {
        case UINavigationControllerOperationPush:
            animationController.type = AnimationTypePresent;
            return  animationController;
        case UINavigationControllerOperationPop:
            animationController.type = AnimationTypeDismiss;
            return animationController;
        default: return nil;
    }
}

有个问题需要注意下,修改了动画之后你需要指定下是哪个控制器需要这种弹出效果,在上面的代理方法里面注释处,如果不指定就默人修改了所有页面的push动画,这种自定义效果还是比较好的,但是实现起来也比前两种更为复杂,还是一句话,看需求来选择哪种方案实现。

另外:在twoVC中别忘了实现导航栏的隐藏:

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    
    [self.navigationController setNavigationBarHidden:YES animated:animated];
}

再在threeVC中实现导航栏的显示:

- (void)viewWillAppear:(BOOL)animated {
    [self.navigationController setNavigationBarHidden:NO animated:animated];
}

好了,三种方案都已经介绍完了,希望可以对大家有所帮助。

你可能感兴趣的:(有关UINavigationController页面之间的跳转动画)