iOS視圖控制器詳解
視圖控制器中的視圖顯示在屏幕上有兩種方式:最主要的方式是內(nèi)嵌在容器控制器中,比如 UINavigationController,UITabBarController, UISplitController;由另外一個(gè)視圖控制器顯示它,這種方式通常被稱為模態(tài)(Modal)顯示;具體方式是在 NavigationController 里 push 或 pop 一個(gè) View Controller,在 TabBarController 中切換到其他 View Controller,以 Modal 方式顯示另外一個(gè) View Controller,這些都是 View Controller Transition。在 storyboard 里,每個(gè) View Controller 是一個(gè) Scene,View Controller Transition 便是從一個(gè) Scene 轉(zhuǎn)換到另外一個(gè) Scene;官網(wǎng)鏈接View Controller Programming Guide for iOS;
觸發(fā)轉(zhuǎn)場(chǎng)的方式
目前為止,官方支持以下幾種transition方式
1.在 UINavigationController 中 push 和 pop;
2.在 UITabBarController 中切換 Tab;
3.Modal transition:presentation 和 dismissal ,稱為視圖控制器的模態(tài)顯示和消失,但是它的model類型屬性modalPresentationStyle 只能限定在UIModalPresentationFullScreen 或 UIModalPresentationCustom 這兩種模式;
以上三種transition都需要代理和動(dòng)畫控制器才可以實(shí)現(xiàn)自定義動(dòng)畫,觸發(fā)的方式分為三種
1)代碼里調(diào)用相關(guān)動(dòng)作的方法
2)Segue
3)容器 VC,在 UINavigationBar 和 UITabBar 上的相關(guān) Item 的點(diǎn)擊操作
相關(guān)動(dòng)作方法
UINavigationController 中所有修改其viewControllers棧中 VC 的方法,就可以自定義transition動(dòng)畫:
下面分別是push和pop方法
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated;
- (nullable UIViewController *)popViewControllerAnimated:(BOOL)animated;
- (void)setViewControllers:(NSArray*)viewControllers animated:(BOOL)animate;//這個(gè)方法是對(duì)VC棧的整體更新
- (nullable NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated
UITabBarController
@property(nullable, nonatomic, assign) __kindof UIViewController *selectedViewController;//傳遞的參數(shù)必須是其下的子VC
@property(nonatomic) NSUInteger selectedIndex;//選中控制器的索引
- (void)setViewControllers:(NSArray<__kindof UIViewController *> * __nullable)viewControllers animated:(BOOL)animated;//和上面的差不多意思
Modal
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^ __nullable)(void))completion NS_AVAILABLE_IOS(5_0);//Presentation
- (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^ __nullable)(void))completion NS_AVAILABLE_IOS(5_0);//dismiss
Segue
這種方式是在storyboard里面設(shè)置的:存在兩種方式(transition發(fā)生前修改轉(zhuǎn)場(chǎng)參數(shù)的最后機(jī)會(huì))
performSegueWithIdentifier:sender:
prepareForSegue:sender
Transition解釋
transition過(guò)程之中,作為容器的父 VC 維護(hù)著多個(gè)子 VC,但在視圖結(jié)構(gòu)上,只保留一個(gè)子 VC 的視圖,所以轉(zhuǎn)場(chǎng)的本質(zhì)是下一場(chǎng)景(子 VC)的視圖替換當(dāng)前場(chǎng)景(子 VC)的視圖以及相應(yīng)的控制器(子 VC)的替換,表現(xiàn)為當(dāng)前視圖消失和下一視圖出現(xiàn),基于此進(jìn)行動(dòng)畫,動(dòng)畫的方式非常多;
iOS 7 以協(xié)議的方式開(kāi)放了自定義轉(zhuǎn)場(chǎng)的 API,協(xié)議的好處是不再拘泥于具體的某個(gè)類,只要是遵守該協(xié)議的對(duì)象都能參與轉(zhuǎn)場(chǎng),非常靈活。轉(zhuǎn)場(chǎng)協(xié)議由5種協(xié)議組成,在實(shí)際中只需要我們提供其中的兩個(gè)或三個(gè)便能實(shí)現(xiàn)絕大部分的transiton動(dòng)畫:
1.Transition代理(Transition Delegate):
實(shí)現(xiàn)自定義Transition的第一步就是提供代理,使用我們自己提供的代理,而不是系統(tǒng)默認(rèn)的的代理
//UINavigationController 的 delegate 屬性遵守該協(xié)議。
//UITabBarController 的 delegate 屬性遵守該協(xié)議。
//UIViewController 的 transitioningDelegate 屬性遵守該協(xié)議。(iOS7新增的)
Transition發(fā)生時(shí)候,UIKit要求代理提供transition動(dòng)畫的構(gòu)件:動(dòng)畫控制器和交互控制器(可選的)
2.動(dòng)畫控制器(Animation Controller)
負(fù)責(zé)添加視圖與及執(zhí)行動(dòng)畫:遵守<UIViewControllerAnimatedTransitioning>協(xié)議
3.交互控制器(Interaction Controller)
通過(guò)交互手段,來(lái)控制動(dòng)畫,遵守<UIViewControllerInteractiveTransitioning>協(xié)議;
4.Transition 上下文(Transition Context)
提供Transition過(guò)程中需要的數(shù)據(jù);遵守<UIViewControllerContextTransitioning>協(xié)議;
5.Transition 協(xié)調(diào)器(Transition Coordinator)
可以在Transition動(dòng)畫發(fā)生的同時(shí)執(zhí)行其他動(dòng)畫;遵守<UIViewControllerTransitionCoordinator>協(xié)議,在IOS7中新增了方法transitionCoordinator()返回一個(gè)遵守協(xié)議的對(duì)象,并且該方法只在控制器Transition 的過(guò)程中才返回一個(gè)類對(duì)象;否則返回nil
非交互Transition
這個(gè)階段需要做兩件事,提供Transition代理,并由代理提供動(dòng)畫控制器(交互控制器和動(dòng)畫控制器是可選實(shí)現(xiàn)的),沒(méi)有實(shí)現(xiàn)或者返回ni的話則使用默認(rèn)的Transition效果。總的來(lái)說(shuō),動(dòng)畫控制器是表現(xiàn)的核心部分,代理方法也非常簡(jiǎn)單,讓我們先從動(dòng)畫控制器入手;
動(dòng)畫控制器協(xié)議
動(dòng)畫控制器負(fù)責(zé)添加視圖以及執(zhí)行動(dòng)畫,遵守UIViewControllerAnimatedTransitioning協(xié)議,該協(xié)議要求實(shí)現(xiàn)以下方法:
-(void)animateTransition:(id)transitionContext;//執(zhí)行動(dòng)畫的地方,最核心的方法。
-(NSTimeInterval)transitionDuration:(id)transitionContext;//返回動(dòng)畫時(shí)間
-(void)animationEnded:(BOOL)transitionCompleted;////如果實(shí)現(xiàn)了,會(huì)在轉(zhuǎn)場(chǎng)動(dòng)畫結(jié)束后調(diào)用,可以執(zhí)行一些收尾工作。
最重要是第一個(gè)方法,遵守<UIViewControllerContextTransitioning>協(xié)議的transition context對(duì)象,提供需要的重要數(shù)據(jù),參與視圖控制器和transition過(guò)程的狀態(tài)信息
可以通過(guò)transitionContext在-(void)animateTransition:(id)transitionContext 這個(gè)方法里面來(lái)獲取動(dòng)畫控制器需要的重要信息,例如根視圖,根控制器,方法如下:
- (UIView *)containerView; //返回容器視圖,獲取Transition動(dòng)畫發(fā)生的地方;
- (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key;//獲取視圖控制器(通過(guò)UITransitionContextFromViewControllerKey,UITransitionContextToViewControllerKey這兩個(gè)key)
- (nullable __kindof UIView *)viewForKey:(UITransitionContextViewKey)key NS_AVAILABLE_IOS(8_0); //8.0之后誕生的方法來(lái)獲取根控制器的視圖
@note:通過(guò)viewForKey獲取的視圖就是viewControllerForKey返回控制器的根視圖,或者nil(為nil的情況只有在Modal的Transition中才會(huì)出現(xiàn),因?yàn)閏ontainner中的view不包含presentingView),獲取view的方法如下
UIView *containView = [transitionContext containerView];
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *fromView = fromVC.view;
UIView *toView = toVC.view;
上面的fromView和toView其實(shí)是和viewForKey中相應(yīng)key值獲取的是一樣的
Transition的本質(zhì)是下一個(gè)Scene替換當(dāng)前的Scene,從當(dāng)前過(guò)渡到下一個(gè);
fromView:即將消失或者被替換的視圖,對(duì)應(yīng)的控制器是FromVC;
toView:即將顯示或者要替換的視圖,對(duì)應(yīng)的控制器是toVC;
導(dǎo)航控制器(UINavigationController)的Transition
demo中采取的方法是單獨(dú)生成一個(gè)代理類,這個(gè)類遵守<UINavigationControllerDelegate>
-(id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {NSUInteger transitionType = operation;
SlideAnimationController *slide = [[SlideAnimationController alloc]init];
slide.transitionType = transitionType;
return slide;
} ?//該方法返回動(dòng)畫控制器,若返回來(lái)的是nil則使用系統(tǒng)默認(rèn)的效果
@note:改類中實(shí)現(xiàn)代理的方法采用的是storyBoard里面的Object對(duì)象設(shè)置的,如果你使用的是self.navigationController.delegate = [xxxx new],那么在初始化,離開(kāi)該方法之后,delegate將重新變?yōu)閚il,然后就不會(huì)再調(diào)用代理方法,原因是因?yàn)閐elegate是弱引用,如果你不采取在storyBoard里面設(shè)置的話,你可以通過(guò)一個(gè)本地變量來(lái)達(dá)到強(qiáng)引用的效果,但是設(shè)置的時(shí)候應(yīng)該也要小心,viewDidload方法設(shè)置的時(shí)候有坑,有可能控制器self.navigationController = nil,設(shè)置出來(lái)的self.navigationController.delegate 肯定也是nil,所以建議在prepareForSegue:sender: 這里設(shè)置比較好;
Demo地址:NavigationControllerTransition
TabBar導(dǎo)航控制器(UITabBarController)
UITabBarController 的轉(zhuǎn)場(chǎng)代理和 UINavigationController 類似,都是通過(guò)動(dòng)畫控制器提供相應(yīng)的方法完成,demo中的lei遵守<UITabBarControllerDelegate>協(xié)議,但是該協(xié)議里面并沒(méi)有提供滑動(dòng)方向的相關(guān)方法,需要我們根據(jù)相關(guān)屬性來(lái)判斷;
-(id)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {CGFloat fromIndex = [tabBarController.viewControllers indexOfObject:fromVC];
CGFloat toIndex = [tabBarController.viewControllers indexOfObject:toVC];
NSUInteger tabChangeDirection = toIndex < fromIndex ? TransitionTypeLeft : TransitionTypeRight;
SlideAnimationController *slideAnimationController = [[SlideAnimationController alloc]init];
slideAnimationController.transitionType = tabChangeDirection;
return slideAnimationController;
}
代理方法設(shè)置過(guò)程類似;
Modal Transition
Modal Transiton 和上面介紹的兩種是有區(qū)別的,上兩個(gè)例子里面可以通過(guò)containerView 獲取當(dāng)前Transition的容器,并且fromVC和toVC都在容器里面,而在這一點(diǎn)上面Modal是有區(qū)別的,在Modal中presentingVC相當(dāng)于fromVC,presentedVC相當(dāng)于toVC,兩者的視圖結(jié)構(gòu)如下
在Modal Transition里面,我們著重講UIModalPresentationFullScreen模式和UIModalPresentationCustom模式,這兩種在modal transition中的機(jī)制又是不一樣的,UIModalPresentationFullScreen 模式下,Modal Transiton結(jié)束后 fromView 依然主動(dòng)被從視圖結(jié)構(gòu)中移除了,但是UIModalPresentationCustom沒(méi)有移除,就是因?yàn)檫@個(gè)區(qū)別導(dǎo)致處理dismissal的時(shí)候容易出現(xiàn)問(wèn)題;
dismissal Transition 場(chǎng)景
1.FullScreen 模式:presentation 結(jié)束后,presentingView 被主動(dòng)移出視圖結(jié)構(gòu),不過(guò),在 dismissal transition中希望其出現(xiàn)在屏幕上并且在對(duì)其添加動(dòng)畫怎么辦呢?實(shí)際上,你按照容器類 VC 轉(zhuǎn)場(chǎng)里動(dòng)畫控制器里那樣做也沒(méi)有問(wèn)題,就是將其加入 containerView 并添加動(dòng)畫。不用擔(dān)心,結(jié)束后,UIKit 會(huì)自動(dòng)將其恢復(fù)到原來(lái)的位置。
2.Custom 模式:presentation 結(jié)束后,presentingView(fromView) 未被主動(dòng)移出視圖結(jié)構(gòu),在 dismissal 中,注意不要像其他轉(zhuǎn)場(chǎng)中那樣將 presentingView(toView) 加入 containerView 中,否則 dismissal 結(jié)束后本來(lái)可見(jiàn)的 presentingView 將會(huì)隨著 containerView 一起被移除。如果你在 Custom 模式下沒(méi)有注意到這點(diǎn),很容易出現(xiàn)黑屏之類的現(xiàn)象而不知道問(wèn)題所在。
雖然ios8以上的系統(tǒng),可以通過(guò)UIPresentationController類并重寫以下方法并返回true可以解決上述問(wèn)題:
// Indicate whether the view controller's view we are transitioning from will be removed from the window in the end of the presentation transition ?(Default: NO)
- (BOOL)shouldRemovePresentersView
@note UIPresentationController 類的作用并沒(méi)有改變上面所說(shuō)的presentingview和containerView的層次關(guān)系,但是能修復(fù)這個(gè)問(wèn)題,應(yīng)該是返回YES之后,同時(shí)對(duì)兩個(gè)視圖進(jìn)行控制
結(jié)論:盡量不要在Modal Transiton中的custome模式中對(duì)presentingView進(jìn)行動(dòng)畫,并且針對(duì)custom模式下的使用,官文文檔比較詳細(xì)Creating Custom Presenting
Demo:SLCustomModalTransitionDemo
交互式Transition
在非交互Transition的基礎(chǔ)之上將之交互需要兩個(gè)條件
1.由轉(zhuǎn)場(chǎng)代理提供交互控制器,這是一個(gè)遵守協(xié)議的對(duì)象,不過(guò)系統(tǒng)已經(jīng)打包好了現(xiàn)成的類UIPercentDrivenInteractiveTransition供我們使用。我們不需要做任何配置,僅僅在Transition代理的相應(yīng)方法中提供一個(gè)該類實(shí)例便能工作。另外交互控制器必須有動(dòng)畫控制器才能工作。
2.交互控制器需要交互手段的配合,常用的是使用手勢(shì);
如果在轉(zhuǎn)場(chǎng)代理中提供了交互控制器,而轉(zhuǎn)場(chǎng)發(fā)生時(shí)并沒(méi)有方法來(lái)驅(qū)動(dòng)轉(zhuǎn)場(chǎng)進(jìn)程(比如手勢(shì)),轉(zhuǎn)場(chǎng)過(guò)程將一直處于開(kāi)始階段無(wú)法結(jié)束,應(yīng)用界面也會(huì)失去響應(yīng):在 NavigationController 中點(diǎn)擊 NavigationBar 也能實(shí)現(xiàn) pop 返回操作,但此時(shí)沒(méi)有了交互手段的支持,Transition過(guò)程卡;可以通過(guò)一個(gè)變量來(lái)標(biāo)記交互狀態(tài),該變量由交互手勢(shì)來(lái)更新?tīng)顟B(tài)
-(instancetype)init {??
? if (self = [super init]) {? ? ?
?? _interactive = false;? ? ? ? _interactionController = [[UIPercentDrivenInteractiveTransition alloc]init];
? ? }? ??
return self;
}
-(id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
? ? NSUInteger transitionType = operation;? ?
?SlideAnimationController *slide = [[SlideAnimationController alloc]init];? ??
slide.transitionType = transitionType;? ?
?return slide;
}-(id)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id)animationController {
return _interactive ? _interactionController : nil;
}
TabBarController的實(shí)現(xiàn)也類似;系統(tǒng)打包好的UIPercentDrivenInteractiveTransition中的控制轉(zhuǎn)場(chǎng)進(jìn)度的方法與轉(zhuǎn)場(chǎng)環(huán)境對(duì)象提供的三個(gè)方法同名,實(shí)際上只是前者調(diào)用了后者的方法而已。系統(tǒng)以一種解耦的方式使得動(dòng)畫控制器,交互控制器,轉(zhuǎn)場(chǎng)環(huán)境對(duì)象互相協(xié)作,我們只需要使用UIPercentDrivenInteractiveTransition的三個(gè)同名方法來(lái)控制進(jìn)度就夠了。如果你要實(shí)現(xiàn)自己的交互控制器,而不是UIPercentDrivenInteractiveTransition的子類,就需要調(diào)用轉(zhuǎn)場(chǎng)環(huán)境的三個(gè)方法來(lái)控制進(jìn)度;
交互手段
在上面NavigationControllerTransition 的demo中的Slide控制器提供動(dòng)畫來(lái)實(shí)現(xiàn)右滑返回的效果,綁定方法如下
-(void)handleEdgePanGesture:(UIScreenEdgePanGestureRecognizer *)gesture {
CGFloat translationX = [gesture translationInView:self.view].x;
CGFloat translationBase = self.view.frame.size.width / 3;
CGFloat translationAbs = translationX > 0 ? translationX : -translationX;
CGFloat percent = translationAbs > translationBase ? 1.0 : translationAbs / translationBase; //根據(jù)移動(dòng)距離計(jì)算交互過(guò)程的進(jìn)度。
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
_navigationDelegate = self.navigationController.delegate;////轉(zhuǎn)場(chǎng)開(kāi)始前獲取代理,一旦轉(zhuǎn)場(chǎng)開(kāi)始,VC 將脫離控制器棧,此后 self.navigationController 返回的是 nil。
_navigationDelegate.interactive = true;//更新交互狀態(tài)
[self.navigationController popViewControllerAnimated:YES];//.如果轉(zhuǎn)場(chǎng)代理提供了交互控制器,它將從這時(shí)候開(kāi)始接管轉(zhuǎn)場(chǎng)過(guò)程。
break;
case UIGestureRecognizerStateChanged:
//更新轉(zhuǎn)場(chǎng)進(jìn)度,進(jìn)度數(shù)值范圍為0.0~1.0。
[_navigationDelegate.interactionController updateInteractiveTransition:percent];//.更新進(jìn)度:
break;
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateEnded:
if (percent > 0.5) {//.結(jié)束轉(zhuǎn)場(chǎng):
//完成轉(zhuǎn)場(chǎng),轉(zhuǎn)場(chǎng)動(dòng)畫從當(dāng)前狀態(tài)繼續(xù)直至結(jié)束。(轉(zhuǎn)場(chǎng)動(dòng)畫從當(dāng)前的狀態(tài)將繼續(xù)進(jìn)行直到動(dòng)畫結(jié)束,轉(zhuǎn)場(chǎng)完成)
[_navigationDelegate.interactionController finishInteractiveTransition];////完成轉(zhuǎn)場(chǎng)。
}else {
//取消轉(zhuǎn)場(chǎng),轉(zhuǎn)場(chǎng)動(dòng)畫從當(dāng)前狀態(tài)返回至轉(zhuǎn)場(chǎng)發(fā)生前的狀態(tài)。(被調(diào)用后,轉(zhuǎn)場(chǎng)動(dòng)畫從當(dāng)前的狀態(tài)回?fù)艿匠跏紶顟B(tài),轉(zhuǎn)場(chǎng)取消。)
[_navigationDelegate.interactionController cancelInteractiveTransition];////或者,取消轉(zhuǎn)場(chǎng)。
}
_navigationDelegate.interactive = false;//無(wú)論轉(zhuǎn)場(chǎng)的結(jié)果如何,恢復(fù)為非交互狀態(tài)。
break;
default:
break;
}
}
@note:眾所周知,app的生命周期是按照一定順序的,但是介入了Transiton的時(shí)候,順序就得不到保證了,本來(lái)正確的生命周期應(yīng)該是如下的:
1.viewWillAppear 2.viewDidAppear 3.viewWillDisappear 4.viewDidDisappear
介入Transition之后,順序變得錯(cuò)綜復(fù)雜,可以參考這個(gè)鏈接查看相關(guān)情況The Inconsistent Order of View Transition Events
總結(jié):綜合前面所講的內(nèi)容,都還沒(méi)有辦法實(shí)現(xiàn)Transition中的任意階段的中斷,并執(zhí)行新的動(dòng)畫;但是通過(guò)下面介紹的Transition Coordinator就可以實(shí)現(xiàn)了這種效果
Transition Coordinator
Transition Coordinator使用的時(shí)間比較少,但是它可以在Transition過(guò)程中的任意階段搜集動(dòng)作并在交互中執(zhí)行;
Modal Transition中UIPresentationController類只能通過(guò)轉(zhuǎn)場(chǎng)協(xié)調(diào)器來(lái)與動(dòng)畫控制器同步,并行執(zhí)行其他動(dòng)畫;
使用這兩個(gè)方法
- (BOOL)animateAlongsideTransition:(void (^ __nullable)(idcontext))animation? ? ? ? ? ? ? ? ? ? ? ? completion:(void (^ __nullable)(idcontext))completion;// This alternative API is needed if the view is not a descendent of the container view AND you require this animation// to be driven by a UIPercentDrivenInteractiveTransition interaction controller.- (BOOL)animateAlongsideTransitionInView:(nullable UIView *)view? ? ? ? ? ? ? ? ? ? ? ? ? ? ? animation:(void (^ __nullable)(idcontext))animation? ? ? ? ? ? ? ? ? ? ? ? ? ? ? completion:(void (^ __nullable)(idcontext))completion;
這里它可以在交互式轉(zhuǎn)場(chǎng)結(jié)束時(shí)執(zhí)行一個(gè)閉包
// When a transition changes from interactive to non-interactive then handler is// invoked. The handler will typically then do something depending on whether or// not the transition isCancelled. Note that only interactive transitions can// be cancelled and all interactive transitions complete as non-interactive// ones. In general, when a transition is cancelled the view controller that was// appearing will receive a viewWillDisappear: call, and the view controller// that was disappearing will receive a viewWillAppear: call.? This handler is// invoked BEFORE the "will" method calls are made.- (void)notifyWhenInteractionEndsUsingBlock: (void (^)(idcontext))handler NS_DEPRECATED_IOS(7_0, 10_0,"Use notifyWhenInteractionChangesUsingBlock");
當(dāng)Transition由交互狀態(tài)轉(zhuǎn)變?yōu)榉墙换顟B(tài)(在手勢(shì)交互過(guò)程中則為手勢(shì)結(jié)束時(shí)),無(wú)論Transition的結(jié)果是完成還是被取消,該方法都會(huì)被調(diào)用;得益于閉包,Transition協(xié)調(diào)器可以在轉(zhuǎn)場(chǎng)過(guò)程中的任意階段搜集動(dòng)作并在交互中止后執(zhí)行。閉包中的參數(shù)是一個(gè)遵守協(xié)議的對(duì)象,該對(duì)象由 UIKit 提供,和前面的Transition環(huán)境對(duì)象作用類似;另外交互狀態(tài)結(jié)束時(shí)并非Transition過(guò)程的終點(diǎn)(此后動(dòng)畫控制器提供的Transition動(dòng)畫根據(jù)交互結(jié)束時(shí)的狀態(tài)繼續(xù)或是返回到初始狀態(tài)),而是由動(dòng)畫控制器來(lái)結(jié)束這一切:
- (void)animationEnded:(BOOL) transitionCompleted;
向非交互階段的平滑過(guò)渡
這部分的功能沒(méi)有實(shí)現(xiàn)過(guò),但是找資料發(fā)現(xiàn)使用UIViewControllerInteractiveTransitioning協(xié)議定義了兩個(gè)屬性可以做到平滑過(guò)渡
completionCurve //交互結(jié)束后剩余動(dòng)畫的速率曲線
completionSpeed //交互結(jié)束后動(dòng)畫的開(kāi)始速率由該參數(shù)與原來(lái)的速率相乘得到,實(shí)際上是個(gè)縮放參數(shù),這里應(yīng)該使用單位變化速率(即你要的速率/距離)。注意:completionSpeed會(huì)影響剩余的動(dòng)畫時(shí)間,而不是之前設(shè)定的轉(zhuǎn)場(chǎng)動(dòng)畫時(shí)間剩下的時(shí)間;當(dāng)completionSpeed很小時(shí)剩余的動(dòng)畫時(shí)間可能會(huì)被拉伸得很長(zhǎng),所以過(guò)濾下較低的速率比較好。如果不設(shè)置兩個(gè)參數(shù),轉(zhuǎn)場(chǎng)動(dòng)畫將以原來(lái)的速率曲線在當(dāng)前進(jìn)度的速率繼續(xù)。不過(guò)從實(shí)際使用效果來(lái)看,往往不到0.5s的動(dòng)畫時(shí)間,基本上看不出什么效果來(lái)。
iOS10全程交互控制
在Transition動(dòng)畫里,非交互Transition與交互Transition之間有著明顯的界限:如果以交互轉(zhuǎn)場(chǎng)開(kāi)始,盡管在交互結(jié)束后會(huì)切換到動(dòng)畫過(guò)程,但之后無(wú)法再次切換到交互過(guò)程,只能等待其結(jié)束;如果以非交互Transition開(kāi)始,在動(dòng)畫結(jié)束前是無(wú)法切換到交互過(guò)程的,只能等待其結(jié)束,但是在2016年的WWDC上面介紹的iOS10打破了這個(gè)局面,相關(guān)鏈接WWDC 2016 Session216:Advances in UIKit Animations And Transitions
讓轉(zhuǎn)場(chǎng)動(dòng)畫在非交互狀態(tài)與交互狀態(tài)之間自由切換很困難,UIViewPropertyAnimator類實(shí)現(xiàn)了需要的所有基礎(chǔ)功能,使得難度降低了許多,Demo中使用一個(gè)UIViewPropertyAnimator對(duì)象,就可以實(shí)現(xiàn)轉(zhuǎn)場(chǎng)動(dòng)畫的全程交互控制,甚至不需交互控制器;下面展示Demo中來(lái)實(shí)現(xiàn)Push和Pop過(guò)程全程交互控制的幾個(gè)重要方法:
// 提供一個(gè) UIViewPropertyAnimator,由它來(lái)執(zhí)行轉(zhuǎn)場(chǎng)動(dòng)畫以及實(shí)現(xiàn)交互控制
[animator pauseAnimation];動(dòng)畫暫停
[animator setReversed:true]; 動(dòng)畫反向
[animator startAnimation];開(kāi)始動(dòng)畫
_fractionComplete = animator.fractionComplete; 更新Transition進(jìn)度
[animator continueAnimationWithTimingParameters:[[UISpringTimingParameters alloc]initWithDampingRatio:0.9 initialVelocity:initialVelocity] durationFactor:0]; 手指離開(kāi)屏幕的速度繼續(xù)執(zhí)行剩下的Transition動(dòng)畫,保障動(dòng)畫協(xié)調(diào)過(guò)渡
Demo:PushAndPop
總結(jié):可能上述描述存在錯(cuò)誤,歡迎指出,感謝大家!