之前介绍动画时提过UIView的转场动画,但是开发中我们碰到更多的viewController的切换,ios中常见的viewcontroller切换有四种:模态视图,导航栏控制器,UITabBarController以及addchildviewcontroller,自定义viewcontroller动画切换也是ios7中的新特性,这里整理下常见的操作,outline如下(本文参考http://onevcat.com/2013/10/vc-transition-in-ios7/,代码下载地址为https://github.com/zanglitao/UIVIewControllerSwitch)
1:基本介绍
2:模态视图自定义动画切换
3:UINavigationController自定义动画切换
4:UITaBarController自定义动画切换
5:模态视图交互式动画切换
6:UINavigationController交互式动画切换
基本介绍
在深入之前,我们先来看看新SDK中有关这部分内容的相关接口以及它们的关系和典型用法。这几个接口和类的名字都比较相似,但是还是能比较好的描述出各自的职能的,一开始的话可能比较迷惑,但是当自己动手实现一两个例子之后,它们之间的关系就会逐渐明晰起来。(相关的内容都定义在UIKit的UIViewControllerTransitioning.h中了)
@protocol UIViewControllerContextTransitioning
这个接口用来提供切换上下文给开发者使用,包含了从哪个VC到哪个VC等各类信息,一般不需要开发者自己实现。具体来说,iOS7的自定义切换目的之一就是切换相关代码解耦,在进行VC切换时,做切换效果实现的时候必须要需要切换前后VC的一些信息,系统在新加入的API的比较的地方都会提供一个实现了该接口的对象,以供我们使用。
对于切换的动画实现来说(这里先介绍简单的动画,在后面我会再引入手势驱动的动画),这个接口中最重要的方法有:
- -(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后这个手势往往会失效,解决方法可以看这篇博客