之前介绍动画时提过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获取需要呈现的视图控制器toVCUIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];//得到toVC完全呈现后的frameCGRect 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:0usingSpringWithDamping:0.6initialSpringVelocity:0options:UIViewAnimationOptionCurveEaseInanimations:^{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 *)navigationControlleranimationControllerForOperation:(UINavigationControllerOperation)operationfromViewController:(UIViewController *)fromVCtoViewController:(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 *)tabBarControlleranimationControllerForTransitionFromViewController:(UIViewController *)fromVCtoViewController:(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 *)navigationControlleranimationControllerForOperation:(UINavigationControllerOperation)operationfromViewController:(UIViewController *)fromVCtoViewController:(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后这个手势往往会失效,解决方法可以看这篇博客

UIVIewController自定义切换效果-b相关推荐

  1. android如何自定义viewpager,Android自定义ViewPager实现个性化的图片切换效果

    第一次见到ViewPager这个控件,瞬间爱不释手,做东西的主界面通通ViewPager,以及图片切换也抛弃了ImageSwitch之类的,开始让ViewPager来做.时间长了,ViewPager的 ...

  2. dplayer js控制 自动全屏_vue-video-player 通过自定义按钮组件实现全屏切换效果【推荐】...

    最近公司的产品上线,一些高级功能在基础版本中不对用户开发,通过视频的形式展示. 产品开发用的是 vue, 经同事介绍使用了vue-video-player视频播放插件,通过 demo案例很快实现了视频 ...

  3. android 自定义酷炫ViewPager切换效果

    Android 自定义特效酷炫ViewPager切换效果,当ViewPager切换时,下面的自定义页面标识图也跟着做动画切换, 效果图如下: 具体代码如下: 自定义ViewPager适配器如下: pa ...

  4. android仿微信的activity平滑水平切换动画,Android实现简单底部导航栏 Android仿微信滑动切换效果...

    Android实现简单底部导航栏 Android仿微信滑动切换效果 发布时间:2020-10-09 19:48:00 来源:脚本之家 阅读:96 作者:丶白泽 Android仿微信滑动切换最终实现效果 ...

  5. swiper切换按钮位置改变_swiper插件自定义切换箭头按钮

    不知道大家在使用swiper时有没有遇到这样一种需求,swiper插件自定义切换箭头按钮,话不多说,直接上gif. 也就是需要把左右切换的箭头移到容器的外面,自定义箭头的样式. 给swiper容器再加 ...

  6. html首页 slider图片切换效果,jQuery插件Slider Revolution实现响应动画滑动图片切换效果...

    jQuery插件Slider Revolution实现响应动画滑动图片切换效果 2018-12-31 编程之家 https://www.jb51.cc 这是一款非常强大的内容切换插件,它基于jQuer ...

  7. 使用 Storyboard Segue 实作 UIViewController 的切换 (实例)

    Storyboard 是在 iOS 5 SDK 中才出现的新名词,它其实就是原本的 Xib 档案(Interface Builder),用来制作介面排版方面的工具,当然在 Storyboard 里也多 ...

  8. html对话框跳转页面,html5各种页面切换效果和模态对话框用法总结

    这篇文章主要为大家介绍了html5各种页面切换效果和模态对话框用法总结,较为详细的介绍了HTML5的各种页面元素的使用技巧,非常具有实用价值,需要的朋友可以参考下 本文详细总结了html5各种页面切换 ...

  9. html5各种页面切换效果和模态对话框

    页面动画: data-transition 属性可以定义页面切换是的动画效果. 例如:<a href="index.html" data-transition="p ...

最新文章

  1. poj1625Censored!(AC自动机+dp)
  2. html读取url中文件,HTML5基础知识 - JavaScript API - File - 读取文件为DataURL
  3. 学python的总结_为什么那么多自学Python的后来都放弃了,总结起来就这些原因
  4. 详解公用表表达式(CTE)
  5. git/ TortoiseGit 在bitbucket.org 使用证书登陆
  6. MOCTF-Web-暴跳老板*
  7. Pytorch基础(三)—— DataSet的应用
  8. 学习Jsoup(一)
  9. 十分钟走进大数据世界
  10. Atitit 机器学习算法分类 五大分类v5 t56.docx Atitit 机器学习算法分类 目录 1. 传统的机器学习算法 vs 深度学习 1 1.1. 传统的机器学习算法包括决策树、聚类、贝
  11. BeanUtils简介
  12. 华为21天云计算培训
  13. 关于 ADXL362 传感器不工作的说明
  14. 10x 程序员工作法 - 总复习 | 重新审视“最佳实践”
  15. js前端实现ios12和ios13+摇一摇功能
  16. 阐述篇-聊聊我眼中の《面试宝典》
  17. oracle11g基于bootstrap$中的ind$表损坏系列五
  18. Linux CentOS 大数据集群的基础配置(3) CHD分布式环境搭建
  19. 计算机网络实验--Cisco Packet Tracer 实验
  20. Linux CreateRepo创建Repomd (xml-rpm-metadata)存储库

热门文章

  1. 在html中加入滚动条,html在div中显示滚动条
  2. python定义一个空数组_python如何创建空数组?
  3. Docker 初步认识
  4. Java中集合删除元素时候关于ConcurrentModificationException的迷惑点
  5. Linux 文件夹含义(转)
  6. 【UWP】拖拽列表项的排序功能实现
  7. 从头开始 启动开源电商项目jShop
  8. [iOS]图片高清度太高, 导致内存过大Crash
  9. 【转】修改版WinXP集体歇业避免遭遇调查
  10. 第22节 三个败家子(22)——霸王无敌