在深刻iOS 7的VC切换效果的新API实现以前,先让咱们回顾下如今的通常作法吧。这能够帮助理解为何iOS7要对VC切换给出新的解决方案,若是您对iOS 5中引入的VC容器比较熟悉的话,能够跳过这节。html
在iOS5和iOS6中,除了标准的Push,Tab和PresentModal以外,通常是使用ChildViewController的方式来完成VC之间切换的过渡效果。ChildViewController和自定义的Controller容器是iOS 5 SDK中加入的,能够用来生成自定义的VC容器,简单来讲典型的一种用法相似这样:ios
//ContainerVC.m [self addChildViewController:toVC]; [fromVC willMoveToParentViewController:nil]; [self.view addSubview:toVC.view]; __weak id weakSelf = self; [self transitionFromViewController:fromVC toViewController:toVC duration:0.3 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{} completion:^(BOOL finished) { [fromVC.view removeFromSuperView]; [fromVC removeFromParentViewController]; [toVC didMoveToParentViewController:weakSelf]; }];
在本身对view进行管理的同时,可使用transitionFromViewController:toViewController:...的Animation block中能够实现一些简单的切换效果。去年年初我写的UIViewController的误用一文中曾经指出相似[viewController.view addSubview:someOtherViewController.view];
这样的代码的存在,通常就是误用VC。这个结论适用于非Controller容器,对于自定义的Controller容器来讲,向当前view上添加其余VC的view是正确的作法(固然不能忘了也将VC自己经过addChildViewController:
方法添加到容器中)。git
VC容器的主要目的是解决将不一样VC添加到同一个屏幕上的需求,以及能够提供一些简单的自定义切换效果。使用VC容器可使view的关系正确,使添加的VC可以正确接收到例如屏幕旋转,viewDidLoad:等VC事件,进而进行正确相应。VC容器确实能够解决一部分问题,可是也应该看到,对于自定义切换效果来讲,这样的解决还有不少不足。首先是代码高度耦合,VC切换部分的代码直接写在container中,难以分离重用;其次可以提供的切换效果比较有限,只能使用UIView动画来切换,管理起来也略显麻烦。iOS 7提供了一套新的自定义VC切换,就是针对这两个问题的。github
在深刻以前,咱们先来看看新SDK中有关这部份内容的相关接口以及它们的关系和典型用法。这几个接口和类的名字都比较类似,可是仍是能比较好的描述出各自的职能的,一开始的话可能比较迷惑,可是当本身动手实现一两个例子以后,它们之间的关系就会逐渐明晰起来。(相关的内容都定义在UIKit的UIViewControllerTransitioning.h中了)mvc
这个接口用来提供切换上下文给开发者使用,包含了从哪一个VC到哪一个VC等各种信息,通常不须要开发者本身实现。具体来讲,iOS7的自定义切换目的之一就是切换相关代码解耦,在进行VC切换时,作切换效果实现的时候必需要须要切换先后VC的一些信息,系统在新加入的API的比较的地方都会提供一个实现了该接口的对象,以供咱们使用。app
对于切换的动画实现来讲(这里先介绍简单的动画,在后面我会再引入手势驱动的动画),这个接口中最重要的方法有:iview
这个接口负责切换的具体内容,也即“切换中应该发生什么”。开发者在作自定义切换效果时大部分代码会是用来实现这个接口。它只有两个方法须要咱们实现:ide
-(NSTimeInterval)transitionDuration:(id < UIViewControllerContextTransitioning >)transitionContext; 系统给出一个切换上下文,咱们根据上下文环境返回这个切换所须要的花费时间(通常就返回动画的时间就行了,SDK会用这个时间来在百分比驱动的切换中进行帧的计算,后面再详细展开)。工具
-(void)animateTransition:(id < UIViewControllerContextTransitioning >)transitionContext; 在进行切换的时候将调用该方法,咱们对于切换时的UIView的设置和动画都在这个方法中完成。测试
这个接口的做用比较简单单一,在须要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接口的对象(其中包含切换时长和如何切换)。后两个方法涉及交互式切换,以后再说。
仍是那句话,一百行的讲解不如一个简单的小Demo,因而..it's demo time~ 整个demo的代码我放到了github的这个页面上,有须要的朋友能够参照着看这篇文章。
咱们打算作一个简单的自定义的modalViewController的切换效果。普通的present modal VC的效果你们都已经很熟悉了,此次咱们先实现一个自定义的相似的modal present的效果,与普通效果不一样的是,咱们但愿modalVC出现的时候不要那么乏味的就简单从底部出现,而是带有一个弹性效果(这里虽然是弹性,可是仅指使用UIView的模拟动画,而不设计iOS 7的另外一个重要特性UIKit Dynamics。用UIKit Dynamics固然也许能够实现更逼真华丽的效果,可是已经超出本文的主题范畴了,所以不在这里展开了。关于UIKit Dynamics,能够参看我以前关于这个主题的一篇介绍)。咱们首先实现简单的ModalVC弹出吧..这段很是基础,就交待了一下背景,非初级人士请跳过代码段..
先定义一个ModalVC,以及相应的protocal和delegate方法:
//ModalViewController.h @class ModalViewController; @protocol ModalViewControllerDelegate <NSObject> -(void) modalViewControllerDidClickedDismissButton:(ModalViewController *)viewController; @end @interface ModalViewController : UIViewController @property (nonatomic, weak) id<ModalViewControllerDelegate> delegate; @end //ModalViewController.m - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.view.backgroundColor = [UIColor lightGrayColor]; UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0); [button setTitle:@"Dismiss me" forState:UIControlStateNormal]; [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:button]; } -(void) buttonClicked:(id)sender { if (self.delegate && [self.delegate respondsToSelector:@selector(modalViewControllerDidClickedDismissButton:)]) { [self.delegate modalViewControllerDidClickedDismissButton:self]; } }
这个是很标准的modalViewController的实现方式了。须要多嘴一句的是,在实际使用中有的同窗喜欢在-buttonClicked:中直接给self发送dismissViewController的相关方法。在如今的SDK中,若是当前的VC是被显示的话,这个消息会被直接转发到显示它的VC去。可是这并非一个好的实现,违反了程序设计的哲学,也很容易掉到坑里,具体案例能够参看这篇文章的评论。
因此咱们用标准的方式来呈现和解散这个VC:
//MainViewController.m - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0); [button setTitle:@"Click me" forState:UIControlStateNormal]; [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:button]; } -(void) buttonClicked:(id)sender { ModalViewController *mvc = [[ModalViewController alloc] init]; mvc.delegate = self; [self presentViewController:mvc animated:YES completion:nil]; } -(void)modalViewControllerDidClickedDismissButton:(ModalViewController *)viewController { [self dismissViewControllerAnimated:YES completion:nil]; }
测试一下,没问题,而后咱们能够开始实现自定义的切换效果了。首先咱们须要一个实现了UIViewControllerAnimatedTransitioning的对象..嗯,新建一个类来实现吧,好比BouncePresentAnimation:
//BouncePresentAnimation.h @interface BouncePresentAnimation : NSObject<UIViewControllerAnimatedTransitioning> @end //BouncePresentAnimation.m - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return 0.8f; } - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { // 1. Get controllers from transition context UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; // 2. Set init frame for toVC CGRect screenBounds = [[UIScreen mainScreen] bounds]; CGRect finalFrame = [transitionContext finalFrameForViewController:toVC]; toVC.view.frame = CGRectOffset(finalFrame, 0, screenBounds.size.height); // 3. Add toVC's view to containerView UIView *containerView = [transitionContext containerView]; [containerView addSubview:toVC.view]; // 4. Do animate now NSTimeInterval duration = [self transitionDuration:transitionContext]; [UIView animateWithDuration:duration delay:0.0 usingSpringWithDamping:0.6 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ toVC.view.frame = finalFrame; } completion:^(BOOL finished) { // 5. Tell context that we completed. [transitionContext completeTransition:YES]; }]; }
解释一下这个实现:
接下来咱们实现一个UIViewControllerTransitioningDelegate,应该就能让它工做了。简单来讲,一个比较好的地方是直接在MainViewController中实现这个接口。在MainVC中声明实现这个接口,而后加入或变动为以下代码:
@interface MainViewController ()<ModalViewControllerDelegate, UIViewControllerTransitioningDelegate> @property (nonatomic, strong) BouncePresentAnimation *presentAnimation; @end @implementation MainViewController - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization _presentAnimation = [BouncePresentAnimation new]; } return self; } -(void) buttonClicked:(id)sender { //... mvc.transitioningDelegate = self; //... } - (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return self.presentAnimation; }
Believe or not, we have done. 跑一下,应该能够获得以下效果:
iOS7引入了一种手势驱动的VC切换的方式(交互式切换)。若是你使用系统的各类应用,在navViewController里push了一个新的VC的话,返回时并不须要点击左上的Back按钮,而是经过从屏幕左侧划向右侧便可完成返回操做。而在这个操做过程当中,咱们甚至能够撤销咱们的手势,以取消此次VC转移。在新版的Safari中,咱们甚至能够用相同的手势来完成网页的后退功能(因此很大程度上来讲屏幕底部的工具栏成为了摆设)。若是您还不知道或者没太留意过这个改动,不妨如今就拿手边的iOS7这辈试试看,手机浏览的朋友记得切回来哦 :)
咱们这就动手在本身的VC切换中实现这个功能吧,首先咱们须要在刚才的知识基础上补充一些东西:
首先是UIViewControllerContextTransitioning,刚才提到这个是系统提供的VC切换上下文,若是您深刻看了它的头文件描述的话,应该会发现其中有三个关于InteractiveTransition的方法,正是用来处理交互式切换的。可是在初级的实际使用中咱们其实能够不太理会它们,而是使用iOS 7 SDK已经给咱们准备好的一个现成转为交互式切换而新加的类:UIPercentDrivenInteractiveTransition。
这是一个实现了UIViewControllerInteractiveTransitioning接口的类,为咱们预先实现和提供了一系列便利的方法,能够用一个百分比来控制交互式切换的过程。通常来讲咱们更多地会使用某些手势来完成交互式的转移(固然用的高级的话用其余的输入..好比声音,iBeacon距离或者甚至面部微笑来作输入驱动也无不可,毕竟想象无极限嘛..),这样使用这个类(通常是其子类)的话就会很是方便。咱们在手势识别中只须要告诉这个类的实例当前的状态百分好比何,系统便根据这个百分比和咱们以前设定的迁移方式为咱们计算当前应该的UI渲染,十分方便。具体的几个重要方法:
就如上面提到的,UIPercentDrivenInteractiveTransition只是实现了这个接口的一个类。为了实现交互式切换的功能,咱们须要实现这个接口。由于大部分时候咱们其实不须要本身来实现这个接口,所以在这篇入门中就不展开说明了,有兴趣的童鞋能够自行钻研。
还有就是上面提到过的UIViewControllerTransitioningDelegate中的返回Interactive实现对象的方法,咱们一样会在交互式切换中用到它们。
Demo time again。在刚才demo的基础上,此次咱们用一个向上划动的手势来吧以前呈现的ModalViewController给dismiss掉~固然是交互式的切换,能够半途取消的那种。
首先新建一个类,继承自UIPercentDrivenInteractiveTransition,这样咱们能够省很多事儿。
//SwipeUpInteractiveTransition.h @interface SwipeUpInteractiveTransition : UIPercentDrivenInteractiveTransition @property (nonatomic, assign) BOOL interacting; - (void)wireToViewController:(UIViewController*)viewController; @end //SwipeUpInteractiveTransition.m @interface SwipeUpInteractiveTransition() @property (nonatomic, assign) BOOL shouldComplete; @property (nonatomic, strong) UIViewController *presentingVC; @end @implementation SwipeUpInteractiveTransition -(void)wireToViewController:(UIViewController *)viewController { self.presentingVC = viewController; [self prepareGestureRecognizerInView:viewController.view]; } - (void)prepareGestureRecognizerInView:(UIView*)view { UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)]; [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: // 1. Mark the interacting flag. Used when supplying it in delegate. self.interacting = YES; [self.presentingVC dismissViewControllerAnimated:YES completion:nil]; break; case UIGestureRecognizerStateChanged: { // 2. Calculate the percentage of guesture CGFloat fraction = translation.y / 400.0; //Limit it between 0 and 1 fraction = fminf(fmaxf(fraction, 0.0), 1.0); self.shouldComplete = (fraction > 0.5); [self updateInteractiveTransition:fraction]; break; } case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateCancelled: { // 3. Gesture over. Check if the transition should happen or not self.interacting = NO; if (!self.shouldComplete || gestureRecognizer.state == UIGestureRecognizerStateCancelled) { [self cancelInteractiveTransition]; } else { [self finishInteractiveTransition]; } break; } default: break; } } @end
有点长,可是作的事情仍是比较简单的。
接下来咱们须要添加一个向下移动的UIView动画,用来表现dismiss。这个十分简单,和BouncePresentAnimation很类似,写一个NormalDismissAnimation的实现了UIViewControllerAnimatedTransitioning接口的类就能够了,本文里略过不写了,感兴趣的童鞋能够自行查看源码。
最后调整MainViewController的内容,主要修改点有三个地方:
//MainViewController.m @interface MainViewController ()<ModalViewControllerDelegate,UIViewControllerTransitioningDelegate> //... // 1. Add dismiss animation and transition controller @property (nonatomic, strong) NormalDismissAnimation *dismissAnimation; @property (nonatomic, strong) SwipeUpInteractiveTransition *transitionController; @end @implementation MainViewController - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { //... _dismissAnimation = [NormalDismissAnimation new]; _transitionController = [SwipeUpInteractiveTransition new]; //... } -(void) buttonClicked:(id)sender { //... // 2. Bind current VC to transition controller. [self.transitionController wireToViewController:mvc]; //... } // 3. Implement the methods to supply proper objects. -(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed { return self.dismissAnimation; } -(id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator { return self.transitionController.interacting ? self.transitionController : nil; }
完成了,若是向下划动时,效果以下:
demo中只展现了对于modalVC的present和dismiss的自定义切换效果,固然对与Navigation Controller的Push和Pop切换也是有相应的一套方法的。实现起来和dismiss十分相似,只不过对应UIViewControllerTransitioningDelegate的询问动画和交互的方法换到了UINavigationControllerDelegate中(为了区别push或者pop,看一下这个接口应该能立刻知道)。另一个很好的福利是,对于标准的navController的Pop操做,苹果已经替咱们实现了手势驱动返回,咱们不用再费心每一个去实现一遍了,cheers~
另外,可能你会以为使用VC容器其提供的transition动画方法来进行VC切换就已经够好够方便了,为何iOS7中还要引入一套自定义的方式呢。其实从根原本说它们所承担的是两类彻底不一样的任务:自定义VC容器能够提供本身定义的VC结构,并保证系统的各种方法和通知可以准确传递到合适的VC,它提供的transition方法虽然能够实现一些简单的UIView动画,可是难以重用,能够说是和containerVC彻底耦合在一块儿的;而自定义切换并不改变VC的组织结构,只是负责提供view的效果,由于VC切换将动画部分、动画驱动部分都使用接口的方式给出,所以重用性很是优秀。在绝大多数状况下,精心编写的一套UIView动画是能够轻易地用在不一样的VC中,甚至是不一样的项目中的。
须要特别一提的是,Github上的ColinEberhardt的VCTransitionsLibrary已经为咱们提供了一系列的VC自定义切换动画效果,正是得益于iOS7中这一块的良好设计(虽然这几个接口的命名比较类似,在弄明白以前会有些confusing),所以这些效果使用起来很是方便,相信通常项目中是足够使用的了。而其余更复杂或者炫目的效果,亦可在其基础上进行扩展改进获得。能够说随着愈来愈多的应用转向iOS7,自定义VC切换将成为新的用户交互实现的基础和重要部分,对于从此会在其基础上会衍生出怎样让人眼前一亮的交互设计,不妨让咱们拭目以待(或者本身努力去创造)。