學(xué)習(xí)了seedante大神的《iOS 視圖控制器轉(zhuǎn)場詳解》https://github.com/seedante/iOS-Note/wiki/ViewController-Transition,覺得iOS的自定義轉(zhuǎn)場功能十分強(qiáng)大,可以實(shí)現(xiàn)許多絢麗的動畫效果。seedante大神的博客中代碼是用的Swift語言編寫的,此文中介紹的側(cè)滑效果用ObjectiveC實(shí)現(xiàn),關(guān)鍵性的地方有參考該文章。


在學(xué)習(xí)轉(zhuǎn)場之前首先要理清楚轉(zhuǎn)場中的一些關(guān)鍵詞:
1.轉(zhuǎn)場代理(Transition Delegate):自定義轉(zhuǎn)場的第一步便是提供轉(zhuǎn)場代理,告訴系統(tǒng)使用我們提供的代理而不是系統(tǒng)的默認(rèn)代理來執(zhí)行轉(zhuǎn)場。有如下三種轉(zhuǎn)場代理,對應(yīng)上面三種類型的轉(zhuǎn)場:
UINavigationControllerDelegate //UINavigationController 的 delegate 屬性遵守該協(xié)議。
UITabBarControllerDelegate //UITabBarController 的 delegate 屬性遵守該協(xié)議。
UIViewControllerTransitioningDelegate //UIViewController 的 transitioningDelegate 屬性遵守該協(xié)議。
這里除了UIViewControllerTransitioningDelegate是 iOS 7 新增的協(xié)議,其他兩種在 iOS 2 里就存在了,在 iOS 7 時(shí)擴(kuò)充了這兩種協(xié)議來支持自定義轉(zhuǎn)場。轉(zhuǎn)場發(fā)生時(shí),UIKit 將要求轉(zhuǎn)場代理將提供轉(zhuǎn)場動畫的核心構(gòu)件:動畫控制器和交互控制器(可選的);由我們實(shí)現(xiàn)。
2.動畫控制器(Animation Controller):最重要的部分,負(fù)責(zé)添加視圖以及執(zhí)行動畫;遵守UIViewControllerAnimatedTransitioning協(xié)議;由我們實(shí)現(xiàn)。
3.交互控制器(Interaction Controller):通過交互手段,通常是手勢來驅(qū)動動畫控制器實(shí)現(xiàn)的動畫,使得用戶能夠控制整個(gè)過程;遵守UIViewControllerInteractiveTransitioning協(xié)議;系統(tǒng)已經(jīng)打包好現(xiàn)成的類供我們使用。
4.轉(zhuǎn)場環(huán)境(Transition Context):提供轉(zhuǎn)場中需要的數(shù)據(jù);遵守UIViewControllerContextTransitioning協(xié)議;由 UIKit 在轉(zhuǎn)場開始前生成并提供給我們提交的動畫控制器和交互控制器使用。
5.轉(zhuǎn)場協(xié)調(diào)器(Transition Coordinator):可在轉(zhuǎn)場動畫發(fā)生的同時(shí)并行執(zhí)行其他的動畫,其作用與其說協(xié)調(diào)不如說輔助,主要在 Modal 轉(zhuǎn)場和交互轉(zhuǎn)場取消時(shí)使用,其他時(shí)候很少用到;遵守UIViewControllerTransitionCoordinator協(xié)議;由 UIKit 在轉(zhuǎn)場時(shí)生成,UIViewController 在 iOS 7 中新增了方法transitionCoordinator()返回一個(gè)遵守該協(xié)議的對象,且該方法只在該控制器處于轉(zhuǎn)場過程中才返回一個(gè)此類對象,不參與轉(zhuǎn)場時(shí)返回 nil。
結(jié)合這張圖:

下面是用交互轉(zhuǎn)場實(shí)現(xiàn)的側(cè)滑效果。

相較于一般的非交互轉(zhuǎn)場,交互轉(zhuǎn)場在UIViewControllerTransitioningDelegate協(xié)議里面多實(shí)現(xiàn)了一下兩個(gè)方法:
- (nullable id )interactionControllerForPresentation:(id )animator;
-(nullable id )interactionControllerForDismissal:(id )animator;
用來返回一個(gè)UIViewControllerInteractiveTransitioning交互控制器,該類可以控制轉(zhuǎn)場過程的進(jìn)度,在使用中,只需返回一個(gè)UIPercentDrivenInteractiveTransition對象即可。該對象通過以下幾個(gè)方法來控制轉(zhuǎn)場過程的進(jìn)度:
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
- (void)cancelInteractiveTransition;
-(void)finishInteractiveTransition;
調(diào)用第一個(gè)成員方法需要給它一個(gè)進(jìn)度的參數(shù),告訴代理此轉(zhuǎn)場應(yīng)該進(jìn)度一個(gè)多少,一般可以將此函數(shù)加入到手勢里面調(diào)用。后面兩個(gè)方法,可以直接調(diào)用,完成的結(jié)果和非交互的轉(zhuǎn)場效果一樣。
以下是在UIScreenEdgePanGestureRecognizer手勢里面調(diào)用:
-(void)handleAction:(UIScreenEdgePanGestureRecognizer *)gesture
{
?? ? CGFloat translationX = [gesture translationInView:_bottomVc.view].x;
? ? CGFloat translationBase = _bottomVc.view.frame.size.width;
? ? CGFloat translationAbs = translationX > 0 ? translationX : -translationX;
? ? CGFloat percent = translationAbs > translationBase ? 1.0:translationAbs/translationBase;
?? ? if (gesture.state == UIGestureRecognizerStateBegan) {
? ? ? ? ?[_bottomVc presentViewController:self.navigation animated:YES completion:nil];
? ? ?}
? ? ? ?if (gesture.state == UIGestureRecognizerStateChanged) {
? ? ? ? ? ? ? ? ? ? ?[self.interaction updateInteractiveTransition:percent];
? ? ? ? ?}
? ? ? ? ?if (gesture.state == UIGestureRecognizerStateEnded) {
? ? ? ? if (percent > 0.5) {
? ? ? ? ? [self.interaction finishInteractiveTransition];
? ? ? ? ?}? ? ? ? else
? ? ? ? ? ? ? [self.interaction cancelInteractiveTransition];
? ? ? ? ? ? NSLog(@"%@",_bottomVc.view);
? ? ? ?}}
轉(zhuǎn)場的代理設(shè)置如下:
_navigation.modalPresentationStyle = UIModalPresentationCustom;
_navigation.transitioningDelegate = self;
在使用UIModalPresentationCustom模式進(jìn)行轉(zhuǎn)場的時(shí)候有一個(gè)注意點(diǎn),就是在UIViewControllerAnimatedTransitioning動畫控制器中實(shí)現(xiàn)代理方法?- (void)animateTransition:(id )transitionContext;時(shí),當(dāng)用UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];來獲得fromView,在轉(zhuǎn)場進(jìn)入的時(shí)候,會為空。所以要使用[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];在返回控制器的View。(注意,在轉(zhuǎn)場出現(xiàn)和轉(zhuǎn)場退出的時(shí)候,fromView和toVc是互換的,但是在轉(zhuǎn)場退出時(shí),fromView還是會為空)。
我們可以在類中設(shè)置一個(gè)成員變量來保持上下文UIViewControllerContextTransitioning,在
-(void)animationEnded:(BOOL)transitionCompleted
{
NSLog(@"toView %@",[_context viewForKey:UITransitionContextToViewKey]);
NSLog(@"fromView %@",[_context viewForKey:UITransitionContextFromViewKey]);
NSLog(@"toVc %@",[_context viewControllerForKey:UITransitionContextToViewControllerKey]);
NSLog(@"fromVc %@",[_context viewControllerForKey:UITransitionContextFromViewControllerKey]);
NSLog(@"toView--vc %@",[_context viewControllerForKey:UITransitionContextToViewControllerKey].view);
NSLog(@"fromView--vc %@",[_context viewControllerForKey:UITransitionContextFromViewControllerKey].view);
}
可以看不同調(diào)用方式的情況。
在轉(zhuǎn)場完成和轉(zhuǎn)場退出后,之前的presentedView會丟失,為了在側(cè)滑時(shí)可用同時(shí)顯示兩個(gè)界面,在轉(zhuǎn)場動畫中應(yīng)該設(shè)置一個(gè)靜態(tài)的UIView用來保存presentingView的父視圖,在[toVc isBeingPresented]的條件下,我們進(jìn)行一個(gè)保存presentingView = fromView.superview;然后在動畫完成時(shí)添加[presentingView addSubview:fromView];,在[fromVc isBeingDismissed]的條件下,動畫完成時(shí)應(yīng)該這樣添加
BOOL isCancel = [transitionContext transitionWasCancelled];
?if (!isCancel) {
[presentingView addSubview:toView];
[fromView removeFromSuperview]; }
最后,代碼請到我的Github里面下載,里面還實(shí)現(xiàn)了幾個(gè)非交互轉(zhuǎn)場的方式,都是基于ObjectiveC語言的,歡迎大家交流學(xué)習(xí)。
Github:??https://github.com/AirChen/ACTransition
下面這個(gè)是以側(cè)滑為基礎(chǔ),模仿QQ的側(cè)滑效果:
