最近遇到一个需求,一个导航控制器上有一个按钮点击弹出一个全屏幕的蒙层,然后蒙层上有个按钮点击从屏幕右边push过来一个页面;
先来张草图感受下需求是什么(很直观有木有):
我们可以形象的举个,如果蒙层我们也算做一个控制器的话,有三个控制器,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 <Foundation/Foundation.h>
typedef enum {
AnimationTypePresent,
AnimationTypeDismiss
} AnimationType;
@interface CustomAnimator : NSObject<UIViewControllerAnimatedTransitioning>
@property (nonatomic, assign) AnimationType type;
@end
//CustomAnimator.m
@implementation CustomAnimator
// 系统给出一个切换上下文,我们根据上下文环境返回这个切换所需要的花费时间
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 1.0;
}
// 完成容器转场动画的主要方法,我们对于切换时的UIView的设置和动画都在这个方法中完成
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)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<UIViewControllerAnimatedTransitioning>)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];
}
好了,三种方案都已经介绍完了,希望可以对大家有所帮助。