關於動畫在iOS開發中的應用,曾經整理過一系列的博客進行總結。包括簡單的UIView層的動畫,CALayer層的動畫,Autolayout自動佈局動畫以及CoreAnimation核心動畫框架等。本篇博客主要深刻討論視圖控制器、導航控制器來進行界面跳轉時的專場動畫相關內容。以前的動畫相關博客列舉以下:編程
iOS動畫開發之一——UIViewAnimation動畫的使用:http://www.javashuo.com/article/p-mhgdzmkw-hy.html框架
iOS動畫開發之二——UIView動畫執行的另外一種方式:http://www.javashuo.com/article/p-tmehfbif-es.html函數
iOS動畫開發之三——UIView的轉場切換:http://www.javashuo.com/article/p-ymkfcyhc-gw.html佈局
iOS動畫開發之四——核心動畫編程(CoreAnimation):http://www.javashuo.com/article/p-ailnsekr-hx.html動畫
iOS動畫開發之五——炫酷的粒子效果:http://www.javashuo.com/article/p-pecotnsr-cx.htmlatom
iOS開發CoreAnimation解讀之一——初識CoreAnimation核心動畫編程:http://www.javashuo.com/article/p-yawoejry-bw.htmlurl
iOS開發CoreAnimation解讀之二——對CALayer的分析:http://www.javashuo.com/article/p-vfwuxjxq-t.htmlspa
iOS開發CoreAnimation解讀之三——幾種經常使用Layer的使用解析:http://www.javashuo.com/article/p-cszuhugx-bg.html.net
iOS開發CoreAnimation解讀之四——Layer層動畫內容:http://www.javashuo.com/article/p-tbridkqp-bp.html設計
iOS開發CoreAnimation解讀之五——高級動畫技巧:http://www.javashuo.com/article/p-oogudvca-ce.html
iOS開發CoreAnimation解讀之五——CATransform3D變換的應用:http://www.javashuo.com/article/p-aslceclu-v.html
iOS中播放gif動態圖的方式探討:http://www.javashuo.com/article/p-rxievqbk-q.html
iOS界面佈局之三——純代碼的autoLayout及佈局動畫:http://www.javashuo.com/article/p-takuywzr-dx.html
開始本篇博客前,先上一張圖,若是你以爲很差理解,不要緊,看完後面的內容再回來看這張圖,就一目瞭然了。
首先,使用CoreAnimation框架中的CATransition類也能夠實現視圖控制器的轉場動畫,前面的博客有過討論,這裏再也不重複。presentViewController這個函數使用率可謂是很是高的,默認的轉場動畫爲新的視圖控制器從下向上彈出,dismissViewControllerAnimated函數的返回動畫則是彈出動畫的逆序播放。其實,系統提供了4種轉場動畫供開發者選擇,經過設置將要彈出的UIViewController實例的以下屬性:
@property(nonatomic,assign) UIModalTransitionStyle modalTransitionStyle;
UIModalTransitionStyle是一個枚舉,以下:
typedef NS_ENUM(NSInteger, UIModalTransitionStyle) { UIModalTransitionStyleCoverVertical = 0, //從下向上彈起 默認項 UIModalTransitionStyleFlipHorizontal __TVOS_PROHIBITED, //水平翻轉 UIModalTransitionStyleCrossDissolve, //漸隱漸現 UIModalTransitionStylePartialCurl NS_ENUM_AVAILABLE_IOS(3_2) __TVOS_PROHIBITED, //翻頁 };
不少時候,上面4種枚舉的轉場動畫樣式並不能知足咱們的需求,咱們可使用UIViewControllerTransitioningDelegate協議來徹底自定義想要的轉場動畫效果。
首先建立一個類,使其遵照UIViewControllerTransitioningDelegate協議,好比我這裏將類名去作TransDelegate,繼承自NSObject。在界面跳轉時,將要彈出的視圖控制器設置以下:
ViewController2 * v2 = [ViewController2 new]; self.transDelegate = [[TransDelegate alloc]init]; v2.transitioningDelegate = self.transDelegate; [self presentViewController:v2 animated:YES completion:nil];
咱們先來看UIViewControllerTransitioningDelegate協議中的以下幾個函數:
//這個函數用來設置當執行present方法時 進行的轉場動畫 /* presented爲要彈出的Controller presenting爲當前的Controller source爲源Contrller 對於present動做 presenting與source是同樣的 */ - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source; //這個函數用來設置當執行dismiss方法時 進行的轉場動畫 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed; //這個函數用來設置當執行present方法時 進行可交互的轉場動畫 - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator; //這個函數用來設置當執行dismiss方法時 進行可交互的轉場動畫 - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator; //iOS8後提供的新接口 返回UIPresentationController處理轉場 - (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(nullable UIViewController *)presenting sourceViewController:(UIViewController *)source NS_AVAILABLE_IOS(8_0);
咱們先來看上面的前兩個函數,這兩個函數都要返回一個實現了UIViewControllerAnimatedTransitioning協議的對象,UIViewControllerAnimatedTransitioning則用來負責具體的動畫展現,例如咱們在建立一個命名爲AniObject的類,繼承自NSObject,使其實現UIViewControllerAnimatedTransitioning協議,在TransDelegate類中實現以下:
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{ return [AniObject new]; }
下面咱們來實現AniObject類來具體的處理動畫效果:
UIViewControllerAnimatedTransitioning協議中有兩個函數是必須實現的,以下:
//這個函數用來設置動畫執行的時長 - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext{ return 2; } //這個函數用來處理具體的動畫 - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{ //跳轉的界面 UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; //最終的位置 CGRect finalRect = [transitionContext finalFrameForViewController:toVC]; //起始位置 toVC.view.frame = CGRectOffset(finalRect, [[UIScreen mainScreen]bounds].size.width, 0); //添加到內容視圖 [[transitionContext containerView]addSubview:toVC.view]; //執行動畫 [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ toVC.view.frame = finalRect; } completion:^(BOOL finished) { //完成動畫 [transitionContext completeTransition:YES]; }]; }
上面咱們實現了一個簡單的自定義轉場動畫,將present動畫修改爲了從右側滑入,可是dismiss動畫依然是默認的從下方劃出。效果以下:
下面咱們來分析下transitionContext這個對象,這個對象其實是一個轉場上下文,使用它來進行動畫的定義和執行:
//容器視圖 用來表現動畫 @property(nonatomic, readonly) UIView *containerView; //下面是幾個只讀屬性 //是否應該執行動畫 @property(nonatomic, readonly, getter=isAnimated) BOOL animated; //是不是可交互的 @property(nonatomic, readonly, getter=isInteractive) BOOL interactive; // This indicates whether the transition is currently interactive. //是否被取消了 @property(nonatomic, readonly) BOOL transitionWasCancelled; //轉場風格 @property(nonatomic, readonly) UIModalPresentationStyle presentationStyle; //調用這個函數來更新轉場過程的百分比 用於可交互動畫的閾值 - (void)updateInteractiveTransition:(CGFloat)percentComplete; //完成可交互的轉場交互動做時調用 - (void)finishInteractiveTransition; //取消可交互的轉場交互動做時調用 - (void)cancelInteractiveTransition; //轉場動畫被中斷 暫停時調用 - (void)pauseInteractiveTransition; //轉場動畫完成時調用 - (void)completeTransition:(BOOL)didComplete; //獲取轉場中的兩個視圖控制器 /* UITransitionContextViewControllerKey的定義 UITransitionContextFromViewControllerKey //原視圖控制器 UITransitionContextToViewControllerKey //跳轉的視圖控制器 */ - (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key; //直接獲取轉場中的視圖 /* UITransitionContextFromViewKey //原視圖 UITransitionContextToViewKey //轉場的視圖 */ - (nullable __kindof UIView *)viewForKey:(UITransitionContextViewKey)key; //獲取視圖控制器的初識位置 - (CGRect)initialFrameForViewController:(UIViewController *)vc; //獲取視圖控制器轉場後的位置 - (CGRect)finalFrameForViewController:(UIViewController *)vc;
經過上面的介紹,咱們可使用UIViewControllerContextTransitioning爲所欲爲的定製轉場動畫,可是還有一個困難咱們沒法克服,那就是能夠交互的轉場動畫。咱們在使用系統的導航控制器時,右劃返回效果對用戶體驗十分友好,咱們下面就來試着將視圖控制器的模態跳轉設計成相似導航可交互的。
首先咱們須要實現TransDelegate類中的以下兩個函數:
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{ return [AniObject new]; } - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator{ //遵照了UIViewControllerInteractiveTransitioning協議的對象 return self.object; }
UIViewControllerInteractiveTransitioning協議用來處理可交互的轉場動畫的具體表現,須要注意,由於使用的是可交互的轉場動畫,UIViewControllerAnimatedTransitioning協議中的animateTransition:方法能夠空實現。下面咱們再建立一個遵照UIViewControllerInteractiveTransitioning協議的類,好比命名爲IntObject,上面代碼中的self.object便是這個類的示例,IntObject.h文件以下:
@interface IntObject : NSObject<UIViewControllerInteractiveTransitioning> -(void)updateAniProgress:(CGFloat)progress; -(void)finish; -(void)cancel; @end
IntObject.m文件實現以下:
@interface IntObject() @property(nonatomic,strong)id<UIViewControllerContextTransitioning> context; @end @implementation IntObject //這個函數用來保存transitionContext -(void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext{ self.context = transitionContext; //跳轉的界面 UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController * fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; //最終的位置 toVC.view.frame = [transitionContext finalFrameForViewController:toVC]; //添加到內容視圖 [[transitionContext containerView]insertSubview:toVC.view belowSubview:fromVC.view]; } //更新動畫狀態 -(void)updateAniProgress:(CGFloat)progress{ UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey]; //最終的位置 CGRect fR = CGRectMake( [UIScreen mainScreen].bounds.size.width*progress, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); frameVC.frame = fR; } //結束轉場 -(void)finish{ [UIView animateWithDuration:0.2 animations:^{ UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey]; frameVC.frame = CGRectMake([UIScreen mainScreen].bounds.size.width, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); } completion:^(BOOL finished) { [self.context completeTransition:YES]; }]; } //取消轉場 -(void)cancel{ [UIView animateWithDuration:0.2 animations:^{ UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey]; frameVC.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); } completion:^(BOOL finished) { [self.context cancelInteractiveTransition]; }]; } @end
下面咱們來添加手勢,在ViewController2類中添加以下代碼:
@interface ViewController2 () @property(nonatomic,strong)UIPanGestureRecognizer * pan; @end @implementation ViewController2 - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor redColor]; [self.view addGestureRecognizer:self.pan]; // Do any additional setup after loading the view. } -(void)pan:(UIPanGestureRecognizer *)pan{ CGPoint translatedPoint = [pan translationInView:self.view]; CGFloat persent = translatedPoint.x / [[UIScreen mainScreen]bounds].size.width; if (persent<0) { return; } persent = fabs(persent); IntObject * obj = [(TransDelegate *)self.transitioningDelegate object]; switch (pan.state) { case UIGestureRecognizerStateBegan:{ [self dismissViewControllerAnimated:YES completion:nil]; break; } case UIGestureRecognizerStateChanged:{ //手勢過程當中,經過updateInteractiveTransition設置pop過程進行的百分比 [obj updateAniProgress:persent]; break; } case UIGestureRecognizerStateEnded:{ //手勢完成後結束標記而且判斷移動距離是否過半,過則finishInteractiveTransition完成轉場操做,否者取消轉場操做 if (persent > 0.5) { [obj finish]; }else{ [obj cancel]; } break; } default: break; } } -(UIPanGestureRecognizer *)pan{ if (!_pan) { _pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)]; } return _pan; } @end
手勢效果以下:
其實,上面演示的是咱們本身建立了一個類來實現UIViewControllerInteractiveTransitioning協議,其實系統也爲咱們提供一個類:UIPercentDrivenInteractiveTransition類,咱們能夠直接調用這個類的以下3個函數而不須要咱們本身重寫了,可是必須實現UIViewControllerAnimatedTransitioning協議中的transitionContext函數來實現動畫效果。
- (void)updateInteractiveTransition:(CGFloat)percentComplete; - (void)cancelInteractiveTransition; - (void)finishInteractiveTransition;
其實現原理與咱們上面進行徹底的自定義是同樣的。
導航轉場動畫的原理與模態跳轉轉場動畫的原理基本是一致的,不一樣的咱們須要設置UINavigationController實例的delegate爲遵照UINavigationControllerDelegate協議的類對象。以後實現以下兩個函數:
//設置轉場的動畫不管是push或pop 返回nil 則使用系統默認的導航轉場動畫 - (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { NSLog(@"sss"); return nil; } //設置可交互的轉場動畫 - (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController{ NSLog(@"aaa"); return nil; }
能夠看到 animationControllerForOperation:函數依然須要返回一個遵照了UIViewControllerAnimatedTransitioning協議的對象,使用方式和前面所介紹的模態跳轉自定義轉場如出一轍。UINavigationControllerOperation這個枚舉將告知開發者導航所作的操做,以下:
typedef NS_ENUM(NSInteger, UINavigationControllerOperation) { UINavigationControllerOperationNone, //無 UINavigationControllerOperationPush, //push操做 UINavigationControllerOperationPop, //pop操做 };
實現UIViewControllerInteractiveTransitioning協議以下:
@interface IntObject() @property(nonatomic,strong)id<UIViewControllerContextTransitioning> context; @end @implementation IntObject -(void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext{ self.context = transitionContext; //跳轉的界面 UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController * fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; //最終的位置 toVC.view.frame = [transitionContext finalFrameForViewController:toVC]; //添加到內容視圖 [[transitionContext containerView]insertSubview:toVC.view belowSubview:fromVC.view]; } -(void)updateAniProgress:(CGFloat)progress{ UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey]; //最終的位置 CGRect fR = CGRectMake( [UIScreen mainScreen].bounds.size.width*progress, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); frameVC.frame = fR; [self.context updateInteractiveTransition:progress]; } -(void)finish{ [UIView animateWithDuration:0.2 animations:^{ UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey]; frameVC.frame = CGRectMake([UIScreen mainScreen].bounds.size.width, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); } completion:^(BOOL finished) { [self.context finishInteractiveTransition]; [self.context completeTransition:YES]; }]; } -(void)cancel{ [UIView animateWithDuration:0.2 animations:^{ UIView *frameVC = [self.context viewForKey:UITransitionContextFromViewKey]; frameVC.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); } completion:^(BOOL finished) { [self.context cancelInteractiveTransition]; [self.context completeTransition:NO]; }]; } @end
如此便可以輕鬆實現可交互的自定義導航動畫。
UITabbar也能夠進行轉場動畫的自定義,須要設置UITabBarController的delegate並實現協議中的以下兩個函數:
//設置非交互的轉場動畫 - (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { } //設置交互的轉場動畫 - (nullable id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController{ }
這兩個函數的應用和導航自定義動畫基本是一致的,這裏就再也不列舉代碼,簡單的效果見下圖: