之前介紹動畫時提過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獲取需要呈現的視圖控制器toVC UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; //得到toVC完全呈現后的frame CGRect 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:0 usingSpringWithDamping:0.6 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ 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 *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(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 *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(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 *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(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后這個手勢往往會失效,解決方法可以看這篇博客