iOS开发中的ViewController转场切换效果实现简介
在iOS7之前,ViewController的切换主要有4种:
- Push/Pop,NavigationViewController
- PresentanddismisModal
- UITabBarController
- addChildViewController(一般用于自定义的继承于UIViewController的容器子类)
iOS5,调用-(void)transitionFromViewController:(UIViewController*)fromViewControllertoViewController:(UIViewController*)toViewControllerduration:(NSTimeInterval)durationoptions:(UIViewAnimationOptions)optionsanimations:(void(^)(void))animationscompletion:(void(^)(BOOLfinished))completionNS_AVAILABLE_IOS(5_0);
(1)前面3种方法这里就不多说了,很常见的系统方法.至于第四种,我在前面文章-剖析网易标签栏的效果中已经做了阐述,但是它提供的容器转场动画只可以实现一些简单的UIView动画,但是难以重用,耦合高.
(2)关键的API:
A.动画控制器(AnimationControllers)遵从UIViewControllerAnimatedTransitioning协议,并且负责实际执行动画。
B.交互控制器(InteractionControllers)通过遵从UIViewControllerInteractiveTransitioning协议来控制可交互式的转场。
C.转场代理(TransitioningDelegates)根据不同的转场类型方便的提供需要的动画控制器和交互控制器。
其中UINavigationControllerDelegatedelegate中新增了2个方法给NavigationController
UIViewControllerTransitioningDelegate新增transitioningDelegate 给Modal的Present和Dismis
UITabBarControllerDelegatedelegate -(id<UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController*)tabBarController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationControllerNS_AVAILABLE_IOS(7_0); -(id<UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController*)tabBarController animationControllerForTransitionFromViewController:(UIViewController*)fromVC toViewController:(UIViewController*)toVCNS_AVAILABLE_IOS(7_0);
D.转场上下文(TransitioningContexts)定义了转场时需要的元数据,比如在转场过程中所参与的视图控制器和视图的相关属性。转场上下文对象遵从UIViewControllerContextTransitioning协议,并且这是由系统负责生成和提供的。
E.转场协调器(TransitionCoordinators)可以在运行转场动画时,并行的运行其他动画。转场协调器遵从UIViewControllerTransitionCoordinator协议。
(3)新的API主要提供了2种VC切换的方式:
A.非交互式切换,即定义一种从一个VC到另一个VC的动画效果,切换的时候自动播放,
B.交互式切换,这种方式同样需要定义动画效果,只是这个动画效果会根据跟随交互式手势来切换VC并同时播放动画效果。iOS7提供了一个默认的基于百分比的动画实现UIPercentDrivenInteractiveTransition,而且根据WWDC的说明,最简单的实现交互式动画的方法就是通过继承UIPercentDrivenInteractiveTransition。
苹果给我们开发者提供的是都是协议接口,所以我们能够很好的单独提出来写成一个个类,在里面实现我们各种自定义效果.
(4)来看看实现UIViewControllerAnimatedTransitioning的自定义动画类
/** * 自定义的动画类 * 实现协议------>@protocolUIViewControllerAnimatedTransitioning * 这个接口负责切换的具体内容,也即“切换中应该发生什么” */ @interfaceMTHCustomAnimator:NSObject<UIViewControllerAnimatedTransitioning> @end @implementationMTHCustomAnimator //系统给出一个切换上下文,我们根据上下文环境返回这个切换所需要的花费时间 -(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext { return1.0; } //完成容器转场动画的主要方法,我们对于切换时的UIView的设置和动画都在这个方法中完成 -(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { //可以看做为destinationViewController UIViewController*toViewController=[transitionContextviewControllerForKey:UITransitionContextToViewControllerKey]; //可以看做为sourceViewController UIViewController*fromViewController=[transitionContextviewControllerForKey:UITransitionContextFromViewControllerKey]; //添加toView到容器上 //如果是XCode6就可以用这段 if([[[UIDevicecurrentDevice]systemVersion]floatValue]>=8.0) { //iOS8SDK新API UIView*toView=[transitionContextviewForKey:UITransitionContextToViewKey]; //UIView*fromView=[transitionContextviewForKey:UITransitionContextFromViewKey]; [[transitionContextcontainerView]addSubview:toView]; }else{ //添加toView到容器上 [[transitionContextcontainerView]addSubview:toViewController.view]; } //如果是XCode5就是用这段 [[transitionContextcontainerView]addSubview:toViewController.view]; toViewController.view.alpha=0.0; [UIViewanimateWithDuration:[selftransitionDuration:transitionContext]animations:^{ //动画效果有很多,这里就展示个左偏移 fromViewController.view.transform=CGAffineTransformMakeTranslation(-320,0); toViewController.view.alpha=1.0; }completion:^(BOOLfinished){ fromViewController.view.transform=CGAffineTransformIdentity; //声明过渡结束-->记住,一定别忘了在过渡结束时调用completeTransition:这个方法 [transitionContextcompleteTransition:![transitionContexttransitionWasCancelled]]; }]; }
PS:从协议中两个方法可以看出,上面两个必须实现的方法需要一个转场上下文参数,这是一个遵从UIViewControllerContextTransitioning协议的对象。通常情况下,当我们使用系统的类时,系统框架为我们提供的转场代理(TransitioningDelegates),为我们创建了转场上下文对象,并把它传递给动画控制器。
//MainViewController @interfaceMTHMainViewController()<UINavigationControllerDelegate,UIViewControllerTransitioningDelegate> @property(nonatomic,strong)MTHCustomAnimator*customAnimator; @property(nonatomic,strong)PDTransitionAnimator*minToMaxAnimator; @property(nonatomic,strong)MTHNextViewController*nextVC; //交互控制器(InteractionControllers)通过遵从UIViewControllerInteractiveTransitioning协议来控制可交互式的转场。 @property(strong,nonatomic)UIPercentDrivenInteractiveTransition*interactionController; @end @implementationMTHMainViewController -(id)initWithNibName:(NSString*)nibNameOrNilbundle:(NSBundle*)nibBundleOrNil { self=[superinitWithNibName:nibNameOrNilbundle:nibBundleOrNil]; if(self){ //Custominitialization } returnself; } -(void)viewDidLoad { [superviewDidLoad]; //Doanyadditionalsetupafterloadingtheview. self.navigationItem.title=@"Demo"; self.view.backgroundColor=[UIColoryellowColor]; //设置代理 self.navigationController.delegate=self; //设置转场动画 self.customAnimator=[[MTHCustomAnimatoralloc]init]; self.minToMaxAnimator=[PDTransitionAnimatornew]; self.nextVC=[[MTHNextViewControlleralloc]init]; //Present的代理和自定义设置 _nextVC.transitioningDelegate=self; _nextVC.modalPresentationStyle=UIModalPresentationCustom;(貌似有BUG)换成modalTransitionStyle=UIModalPresentationCustom //Push UIButton*pushButton=[UIButtonbuttonWithType:UIButtonTypeSystem]; pushButton.frame=CGRectMake(140,200,40,40); [pushButtonsetTitle:@"Push"forState:UIControlStateNormal]; [pushButtonaddTarget:selfaction:@selector(push)forControlEvents:UIControlEventTouchUpInside]; [self.viewaddSubview:pushButton]; //Present UIButton*modalButton=[UIButtonbuttonWithType:UIButtonTypeSystem]; modalButton.frame=CGRectMake(265,500,50,50); [modalButtonsetTitle:@"Modal"forState:UIControlStateNormal]; [modalButtonaddTarget:selfaction:@selector(modal)forControlEvents:UIControlEventTouchUpInside]; [self.viewaddSubview:modalButton]; //实现交互操作的手势 UIPanGestureRecognizer*panRecognizer=[[UIPanGestureRecognizeralloc]initWithTarget:selfaction:@selector(didClickPanGestureRecognizer:)]; [self.navigationController.viewaddGestureRecognizer:panRecognizer]; } -(void)push { [self.navigationControllerpushViewController:_nextVCanimated:YES]; } -(void)modal { [selfpresentViewController:_nextVCanimated:YEScompletion:nil]; } #pragmamark-UINavigationControllerDelegateiOS7新增的2个方法 //动画特效 -(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController*)navigationControlleranimationControllerForOperation:(UINavigationControllerOperation)operationfromViewController:(UIViewController*)fromVCtoViewController:(UIViewController*)toVC { /** * typedefNS_ENUM(NSInteger,UINavigationControllerOperation){ * UINavigationControllerOperationNone, * UINavigationControllerOperationPush, * UINavigationControllerOperationPop, * }; */ if(operation==UINavigationControllerOperationPush){ returnself.customAnimator; }else{ returnnil; } } //交互 -(id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController*)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController { /** * 在非交互式动画效果中,该方法返回nil * 交互式转场,自我理解意思是,用户能通过自己的动作来(常见:手势)控制,不同于系统缺省给定的push或者pop(非交互式) */ return_interactionController; } #pragmamark-TransitioningDelegate(Modal) //前2个用于动画 -(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController*)presentedpresentingController:(UIViewController*)presentingsourceController:(UIViewController*)source { self.minToMaxAnimator.animationType=AnimationTypePresent; return_minToMaxAnimator; } -(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController*)dismissed { self.minToMaxAnimator.animationType=AnimationTypeDismiss; return_minToMaxAnimator; } //后2个用于交互 -(id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id<UIViewControllerAnimatedTransitioning>)animator { return_interactionController; } -(id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator { returnnil; }
以上实现的是非交互的转场,指的是完全按照系统指定的切换机制,用户无法中途取消或者控制进度切换.那怎么来实现交互转场呢:
UIPercentDrivenInteractiveTransition实现了UIViewControllerInteractiveTransitioning接口的类,,可以用一个百分比来控制交互式切换的过程。我们在手势识别中只需要告诉这个类的实例当前的状态百分比如何,系统便根据这个百分比和我们之前设定的迁移方式为我们计算当前应该的UI渲染,十分方便。具体的几个重要方法:
-(void)updateInteractiveTransition:(CGFloat)percentComplete更新百分比,一般通过手势识别的长度之类的来计算一个值,然后进行更新。之后的例子里会看到详细的用法
-(void)cancelInteractiveTransition报告交互取消,返回切换前的状态
–(void)finishInteractiveTransition报告交互完成,更新到切换后的状态
#pragmamark-手势交互的主要实现--->UIPercentDrivenInteractiveTransition -(void)didClickPanGestureRecognizer:(UIPanGestureRecognizer*)recognizer { UIView*view=self.view; if(recognizer.state==UIGestureRecognizerStateBegan){ //获取手势的触摸点坐标 CGPointlocation=[recognizerlocationInView:view]; //判断,用户从右半边滑动的时候,推出下一个VC(根据实际需要是推进还是推出) if(location.x>CGRectGetMidX(view.bounds)&&self.navigationController.viewControllers.count==1){ self.interactionController=[[UIPercentDrivenInteractiveTransitionalloc]init]; // [selfpresentViewController:_nextVCanimated:YEScompletion:nil]; } }elseif(recognizer.state==UIGestureRecognizerStateChanged){ //获取手势在视图上偏移的坐标 CGPointtranslation=[recognizertranslationInView:view]; //根据手指拖动的距离计算一个百分比,切换的动画效果也随着这个百分比来走 CGFloatdistance=fabs(translation.x/CGRectGetWidth(view.bounds)); //交互控制器控制动画的进度 [self.interactionControllerupdateInteractiveTransition:distance]; }elseif(recognizer.state==UIGestureRecognizerStateEnded){ CGPointtranslation=[recognizertranslationInView:view]; //根据手指拖动的距离计算一个百分比,切换的动画效果也随着这个百分比来走 CGFloatdistance=fabs(translation.x/CGRectGetWidth(view.bounds)); //移动超过一半就强制完成 if(distance>0.5){ [self.interactionControllerfinishInteractiveTransition]; }else{ [self.interactionControllercancelInteractiveTransition]; } //结束后一定要置为nil self.interactionController=nil; } }
最后,给大家分享一个动画特效:类似于飞兔云传的发送ViewController切换
@implementationPDTransitionAnimator #defineSwitch_Time1.2 -(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{ returnSwitch_Time; } #defineButton_Width50.f #defineButton_Space10.f -(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{ UIViewController*toViewController=[transitionContextviewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController*fromViewController=[transitionContextviewControllerForKey:UITransitionContextFromViewControllerKey]; UIView*toView=toViewController.view; UIView*fromView=fromViewController.view; if(self.animationType==AnimationTypeDismiss){ //这个方法能够高效的将当前显示的view截取成一个新的view.你可以用这个截取的view用来显示.例如,也许你只想用一张截图来做动画,毕竟用原始的view做动画代价太高.因为是截取了已经存在的内容,这个方法只能反应出这个被截取的view当前的状态信息,而不能反应这个被截取的view以后要显示的信息.然而,不管怎么样,调用这个方法都会比将view做成截图来加载效率更高. UIView*snap=[toViewsnapshotViewAfterScreenUpdates:YES]; [transitionContext.containerViewaddSubview:snap]; [snapsetFrame:CGRectMake([UIScreenmainScreen].bounds.size.width-Button_Width-Button_Space,[UIScreenmainScreen].bounds.size.height-Button_Width-Button_Space,Button_Width,Button_Width)]; [UIViewanimateWithDuration:[selftransitionDuration:transitionContext]animations:^{ [snapsetFrame:[UIScreenmainScreen].bounds]; }completion:^(BOOLfinished){ [UIViewanimateWithDuration:0.5animations:^{ [[transitionContextcontainerView]addSubview:toView]; snap.alpha=0; }completion:^(BOOLfinished){ [snapremoveFromSuperview]; [transitionContextcompleteTransition:![transitionContexttransitionWasCancelled]]; }]; }]; }else{ UIView*snap2=[toViewsnapshotViewAfterScreenUpdates:YES]; [transitionContext.containerViewaddSubview:snap2]; UIView*snap=[fromViewsnapshotViewAfterScreenUpdates:YES]; [transitionContext.containerViewaddSubview:snap]; [UIViewanimateWithDuration:[selftransitionDuration:transitionContext]animations:^{ [snapsetFrame:CGRectMake([UIScreenmainScreen].bounds.size.width-Button_Width-Button_Space+(Button_Width/2),[UIScreenmainScreen].bounds.size.height-Button_Width-Button_Space+(Button_Width/2),0,0)]; }completion:^(BOOLfinished){ [UIViewanimateWithDuration:0.5animations:^{ //snap.alpha=0; }completion:^(BOOLfinished){ [snapremoveFromSuperview]; [snap2removeFromSuperview]; [[transitionContextcontainerView]addSubview:toView]; //切记不要忘记了噢 [transitionContextcompleteTransition:![transitionContexttransitionWasCancelled]]; }]; }]; } }
其中,snapshotViewAfterScreenUpdates方法的解释,我也不是很懂,反正初级来说会用就行,还可以参照下面的解析:
在iOS7以前,获取一个UIView的快照有以下步骤:首先创建一个UIGraphics的图像上下文,然后将视图的layer渲染到该上下文中,从而取得一个图像,最后关闭图像上下文,并将图像显示在UIImageView中。现在我们只需要一行代码就可以完成上述步骤了:
[viewsnapshotViewAfterScreenUpdates:NO];
这个方法制作了一个UIView的副本,如果我们希望视图在执行动画之前保存现在的外观,以备之后使用(动画中视图可能会被子视图遮盖或者发生其他一些变化),该方法就特别方便。
afterUpdates参数表示是否在所有效果应用在视图上了以后再获取快照。例如,如果该参数为NO,则立马获取该视图现在状态的快照,反之,以下代码只能得到一个空白快照:
[viewsnapshotViewAfterScreenUpdates:YES]; [viewsetAlpha:0.0];
由于我们设置afterUpdates参数为YES,而视图的透明度值被设置成了0,所以方法将在该设置应用在视图上了之后才进行快照,于是乎屏幕空空如也。另外就是……你可以对快照再进行快照……继续快照……
继续前面的内容,这一章,主要介绍自定义ViewController容器上视图VC的切换.先来看看系统给我们提供的容器控制器UINavigationController和UITabBarController都有一个NSArray类型的属性viewControllers,很明显,存储的就是需要切换的视图VC.同理,我们定义一个ContainerViewController,是UIViewController的直接子类,用来作为容器依托,额,其他属性定义详见代码吧,这里不多说了.(PS:原先我进行多个自定义视图VC切换的方法,是放置一个UIScrollView,然后把所有childViewController的View的frame的X坐标,依此按320递增,大家可以自行想象下,这样不好的地方,我感觉就是所有的VC一经加载就全部实体化了,而且不会因为被切换变成暂不显示而释放掉)
偷懒下,用storyboard创建的5个childVC
//ContainerViewController @interfaceFTContainerViewController() @property(strong,nonatomic)FTPhotoSenderViewController *photoSenderViewController; @property(strong,nonatomic)FTVideoSenderViewController *videoSenderViewController; @property(strong,nonatomic)FTFileSenderViewController *fileSenderViewController; @property(strong,nonatomic)FTContactSenderViewController *contactSenderViewController; @property(strong,nonatomic)FTClipboardSenderViewController *clipboardSenderViewController; @property(strong,nonatomic)UIViewController *selectedViewController; //当前选择的VC @property(strong,nonatomic)NSArray *viewControllers; //childVC数组 @property(assign,nonatomic)NSInteger currentControllerIndex; //当前选择的VC的数组下标号 @end @implementationFTContainerViewController #pragmamark-ViewLifecycleMethods -(void)viewDidLoad { [superviewDidLoad]; //childVC self.photoSenderViewController=[self.storyboardinstantiateViewControllerWithIdentifier:@"FTPhotoSenderViewController"]; self.videoSenderViewController=[self.storyboardinstantiateViewControllerWithIdentifier:@"FTVideoSenderViewController"]; self.fileSenderViewController=[self.storyboardinstantiateViewControllerWithIdentifier:@"FTFileSenderViewController"]; self.contactSenderViewController=[self.storyboardinstantiateViewControllerWithIdentifier:@"FTContactSenderViewController"]; self.clipboardSenderViewController=[self.storyboardinstantiateViewControllerWithIdentifier:@"FTClipboardSenderViewController"]; //存储childVC的数组 self.viewControllers=@[_photoSenderViewController,_videoSenderViewController,_fileSenderViewController,_contactSenderViewController,_clipboardSenderViewController]; //缺省为下标为0的VC self.selectedViewController=self.selectedViewController?:self.viewControllers[0]; self.currentControllerIndex=0; }
依旧,实现UIViewControllerAnimatedTransitioning协议的Animator类,不过里面换个动画效果,利用iOS7新增的弹簧动画效果:
#import"FTMthTransitionAnimator.h" @implementationFTMthTransitionAnimator staticCGFloatconstkChildViewPadding=16; staticCGFloatconstkDamping=0.5; //damping参数代表弹性阻尼,随着阻尼值越来越接近0.0,动画的弹性效果会越来越明显,而如果设置阻尼值为1.0,则视图动画不会有弹性效果 staticCGFloatconstkInitialSpringVelocity=0.5; //初始化弹簧速率 -(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext { return1.0; } -(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { /** * -viewControllerForKey:我们可以通过他访问过渡的两个ViewController。 * -containerView:两个ViewController的containerView。 * -initialFrameForViewController和finalFrameForViewController是过渡开始和结束时每个ViewController的frame。 */ UIViewController*fromViewController=[transitionContextviewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController*toViewController=[transitionContextviewControllerForKey:UITransitionContextToViewControllerKey]; [[transitionContextcontainerView]addSubview:toViewController.view]; toViewController.view.alpha=0; BOOLgoingRight=([transitionContextinitialFrameForViewController:toViewController].origin.x<[transitionContextfinalFrameForViewController:toViewController].origin.x); CGFloattransDistance=[transitionContextcontainerView].bounds.size.width+kChildViewPadding; CGAffineTransformtransform=CGAffineTransformMakeTranslation(goingRight?transDistance:-transDistance,0); //CGAffineTransformInvert反转 toViewController.view.transform=CGAffineTransformInvert(transform); // toViewController.view.transform=CGAffineTransformTranslate(toViewController.view.transform,(goingRight?transDistance:-transDistance),0); /** * ----------弹簧动画.....------- * 使用由弹簧的运动描述的时序曲线`animations`。当`dampingRatio`为1时,动画将平稳减速到其最终的模型值不会振荡。阻尼比小于1来完全停止前将振荡越来越多。可以使用弹簧的初始速度,以指定的速度在模拟弹簧的端部的物体被移动它附着之前。这是一个单元坐标系,其中1是指行驶总距离的动画在第二。所以,如果你改变一个物体的位置由200PT在这个动画,以及你想要的动画表现得好像物体在动,在100PT/秒的动画开始之前,你会通过0.5。你通常会想通过0的速度。 */ [UIViewanimateWithDuration:[selftransitionDuration:transitionContext]delay:0usingSpringWithDamping:kDampinginitialSpringVelocity:kInitialSpringVelocityoptions:0x00animations:^{ fromViewController.view.transform=transform; fromViewController.view.alpha=0; //CGAffineTransformIdentity 重置,初始化 toViewController.view.transform=CGAffineTransformIdentity; toViewController.view.alpha=1; }completion:^(BOOLfinished){ fromViewController.view.transform=CGAffineTransformIdentity; //声明过渡结束-->记住,一定别忘了在过渡结束时调用completeTransition:这个方法。 [transitionContextcompleteTransition:![transitionContexttransitionWasCancelled]]; }]; } @end
接下来的代码,就是实现自定义容器切换的关键了.通常情况下,当我们使用系统内建的类时,系统框架为我们创建了转场上下文对象,并把它传递给动画控制器。但是在我们这种情况下,我们需要自定义转场动画,所以我们需要承担系统框架的责任,自己去创建这个转场上下文对象。
@interfaceFTMthTransitionContext:NSObject<UIViewControllerContextTransitioning> -(instancetype)initWithFromViewController:(UIViewController*)fromViewControllertoViewController:(UIViewController*)toViewControllergoingRight:(BOOL)goingRight; @property(nonatomic,copy)void(^completionBlock)(BOOLdidComplete); @property(nonatomic,assign,getter=isAnimated)BOOLanimated; @property(nonatomic,assign,getter=isInteractive)BOOLinteractive;//是否交互式 @property(nonatomic,strong)NSDictionary*privateViewControllers; @property(nonatomic,assign)CGRectprivateDisappearingFromRect; @property(nonatomic,assign)CGRectprivateAppearingFromRect; @property(nonatomic,assign)CGRectprivateDisappearingToRect; @property(nonatomic,assign)CGRectprivateAppearingToRect; @property(nonatomic,weak)UIView*containerView; @property(nonatomic,assign)UIModalPresentationStylepresentationStyle; @end @implementationFTMthTransitionContext -(instancetype)initWithFromViewController:(UIViewController*)fromViewControllertoViewController:(UIViewController*)toViewControllergoingRight:(BOOL)goingRight{ if((self=[superinit])){ self.presentationStyle=UIModalPresentationCustom; self.containerView=fromViewController.view.superview; self.privateViewControllers=@{ UITransitionContextFromViewControllerKey:fromViewController, UITransitionContextToViewControllerKey:toViewController, }; //SettheviewframepropertieswhichmakesenseinourspecializedContainerViewControllercontext.Viewsappearfromanddisappeartothesides,correspondingtowheretheiconbuttonsarepositioned.Sotappingabuttontotherightofthecurrentlyselected,makestheviewdisappeartotheleftandthenewviewappearfromtheright.Theanimatorobjectcanchoosetousethistodeterminewhetherthetransitionshouldbegoinglefttoright,orrighttoleft,forexample. CGFloattravelDistance=(goingRight?-self.containerView.bounds.size.width:self.containerView.bounds.size.width); self.privateDisappearingFromRect=self.privateAppearingToRect=self.containerView.bounds; self.privateDisappearingToRect=CGRectOffset(self.containerView.bounds,travelDistance,0); self.privateAppearingFromRect=CGRectOffset(self.containerView.bounds,-travelDistance,0); } returnself; } -(CGRect)initialFrameForViewController:(UIViewController*)viewController{ if(viewController==[selfviewControllerForKey:UITransitionContextFromViewControllerKey]){ returnself.privateDisappearingFromRect; }else{ returnself.privateAppearingFromRect; } } -(CGRect)finalFrameForViewController:(UIViewController*)viewController{ if(viewController==[selfviewControllerForKey:UITransitionContextFromViewControllerKey]){ returnself.privateDisappearingToRect; }else{ returnself.privateAppearingToRect; } } -(UIViewController*)viewControllerForKey:(NSString*)key{ returnself.privateViewControllers[key]; } -(void)completeTransition:(BOOL)didComplete{ if(self.completionBlock){ self.completionBlock(didComplete); } } //非交互式,直接返回NO,因为不允许交互当然也就无法操作进度取消 -(BOOL)transitionWasCancelled{returnNO;} //非交互式,直接不进行操作,只有进行交互,下面3个协议方法才有意义,可参照系统给我们定义好的交互控制器 //@interfaceUIPercentDrivenInteractiveTransition:NSObject<UIViewControllerInteractiveTransitioning> -(void)updateInteractiveTransition:(CGFloat)percentComplete{} -(void)finishInteractiveTransition{} -(void)cancelInteractiveTransition{} @end
OK,准备工作都做好了,为了仿照UIScrollView的滑动切换,但又因为现在展示的是非交互式,我们定义一个swip(轻扫)手势.
UISwipeGestureRecognizer*leftGesture=[[UISwipeGestureRecognizeralloc]initWithTarget:selfaction:@selector(swapController:)]; [leftGesturesetDirection:UISwipeGestureRecognizerDirectionLeft]; [self.viewaddGestureRecognizer:leftGesture]; UISwipeGestureRecognizer*rightGesture=[[UISwipeGestureRecognizeralloc]initWithTarget:selfaction:@selector(swapController:)]; [rightGesturesetDirection:UISwipeGestureRecognizerDirectionRight]; [self.viewaddGestureRecognizer:rightGesture]; [objc]viewplaincopy //响应手势的方法 -(void)swapViewControllers:(UISwipeGestureRecognizer*)swipeGestureRecognizer { if(swipeGestureRecognizer.direction==UISwipeGestureRecognizerDirectionLeft){ if(_currentControllerIndex<4){ _currentControllerIndex++; } NSLog(@"_currentControllerIndex=%ld",(long)_currentControllerIndex); UIViewController*selectedViewController=self.viewControllers[_currentControllerIndex]; NSLog(@"%s__%d__|%@",__FUNCTION__,__LINE__,@"右边"); self.selectedViewController=selectedViewController; }elseif(swipeGestureRecognizer.direction==UISwipeGestureRecognizerDirectionRight){ NSLog(@"%s__%d__|%@",__FUNCTION__,__LINE__,@"左边"); if(_currentControllerIndex>0){ _currentControllerIndex--; } UIViewController*selectedViewController=self.viewControllers[_currentControllerIndex]; self.selectedViewController=selectedViewController; } } //重写selectedViewController的setter -(void)setSelectedViewController:(UIViewController*)selectedViewController { NSParameterAssert(selectedViewController); [self_transitionToChildViewController:selectedViewController]; _selectedViewController=selectedViewController; } //切换操作(自定义的,联想我在前面文章网易标签栏切换中,系统给的transitionFromViewController,是一个道理) -(void)_transitionToChildViewController:(UIViewController*)toViewController { UIViewController*fromViewController=self.childViewControllers.count>0?self.childViewControllers[0]:nil; if(toViewController==fromViewController){ return; } UIView*toView=toViewController.view; [toViewsetTranslatesAutoresizingMaskIntoConstraints:YES]; toView.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; toView.frame=self.view.bounds; //自定义容器的切换,addChildViewController是关键,它保证了你想要显示的VC能够加载到容器中 //而所谓的动画和上下文,只是为了转场的动画效果 //因此,就算用UIScrollView切换,也不能缺少addChildViewController,切记!切记! [fromViewControllerwillMoveToParentViewController:nil]; [selfaddChildViewController:toViewController]; if(!fromViewController){ [self.viewaddSubview:toViewController.view]; [toViewControllerdidMoveToParentViewController:self]; return; } //Animator FTMthTransitionAnimator*transitionAnimator=[[FTMthTransitionAnimatoralloc]init]; NSUIntegerfromIndex=[self.viewControllersindexOfObject:fromViewController]; NSUIntegertoIndex=[self.viewControllersindexOfObject:toViewController]; //Context FTMthTransitionContext*transitionContext=[[FTMthTransitionContextalloc]initWithFromViewController:fromViewControllertoViewController:toViewControllergoingRight:(toIndex>fromIndex)]; transitionContext.animated=YES; transitionContext.interactive=NO; transitionContext.completionBlock=^(BOOLdidComplete){ //因为是非交互式,所以fromVC可以直接直接remove出itsparent'schildrencontrollersarray [fromViewController.viewremoveFromSuperview]; [fromViewControllerremoveFromParentViewController]; [toViewControllerdidMoveToParentViewController:self]; if([transitionAnimatorrespondsToSelector:@selector(animationEnded:)]){ [transitionAnimatoranimationEnded:didComplete]; } }; //转场动画需要以转场上下文为依托,因为我们是自定义的Context,所以要手动设置 [transitionAnimatoranimateTransition:transitionContext]; }
大功告成.
上面展示的就是一个基本的自定义容器的非交互式的转场切换.那交互式的呢?从上面我定义手势定义为swip而不是pan也可以看出,非交互转场,并不能完全实现UIScrollView那种分页式的效果,按照类似百分比的形式来进行fromVC和toVC的切换,因为我们缺少交互控制器.在自定义的容器中,系统是没有提供返回交互控制器的协议给我们的,查了蛮多资料,也没找到给出明确的方法,我认为,要跟实现转场上下文一样,仿照系统方法,自定义的去实现交互式的协议方法.我们就要去思考,系统是如何搭建起这个环境的.