1、介紹ios
在iOS開發中,轉場動畫的使用無處不見,不僅是咱們本身更多的使用UIViewblock動畫實現一個轉場動畫,其實,在咱們實現VC控制器跳轉的時候都是轉場動畫的實現,例如標籤欄控制器的切換、模態動畫present和dismiss、導航控制器的push和pop。實現它們的轉場動畫,只須要實現它們的動畫協議便可,提及來有點太籠統,不如看下面的圖吧:ide
2、分析函數
對於上面的三種類型的控制器,系統都會爲它們設置一個代理,經過這個代理方法去監測它們切換VC的過程,這個過程僅僅是出現和消失的過程,至於這個過程是什麼過渡效果,這個代理是無論的。要想這個過程是有動畫的,那麼在這些過程當中,也就是代理函數中,須要另外再返回一個實現動畫的對象,這個對象必須遵循實現動畫的協議,在這個協議中開發者能夠重寫自定義轉場動畫。下面會慢慢演示這三種類型控制器的自定義轉場動畫。佈局
重寫不可交互轉場動畫的核心協議內容:動畫
//重寫動畫協議 @protocol UIViewControllerAnimatedTransitioning <NSObject> //動畫執行時間 - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext; //自定義動畫效果 - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext; @end
重寫可交互轉場動畫的核心協議內容:atom
//重寫動畫協議 @protocol UIViewControllerInteractiveTransitioning <NSObject> //自定義動畫效果 - (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext; @end
系統提供的一個百分比可交互轉場動畫核心類內容:spa
//系統提供的百分比動畫類,已經遵循了可交互協議 @interface UIPercentDrivenInteractiveTransition : NSObject <UIViewControllerInteractiveTransitioning>
- (void)pauseInteractiveTransition; - (void)updateInteractiveTransition:(CGFloat)percentComplete; - (void)cancelInteractiveTransition; - (void)finishInteractiveTransition; @end
3、轉場動畫View之間的切換3d
4、實現一個自定義的模態動畫代理
一、概述code
正如咱們所知,系統爲咱們提供的模態動畫默認是從底部present出,而後dismiss回到底部。 雖說這個基本可以知足使用,可是若是咱們還想使用其餘形式的模態動畫例如從頂部present出dismiss回到頂部,這個時候就須要對系統默認的轉場動畫進行自定義了。
二、詳解
(1)要自定義模態轉場動畫,首先須要給被模態的控制器設置一個實現了UIViewControllerAnimatedTransitioning協議的代理,這些協議方法能夠監測動畫執行的過程,代理和協議以下:
//代理 @protocol UIViewControllerTransitioningDelegate; @interface UIViewController(UIViewControllerTransitioning) @property (nullable, nonatomic, weak) id <UIViewControllerTransitioningDelegate> transitioningDelegate API_AVAILABLE(ios(7.0)); @end
//協議
@protocol UIViewControllerTransitioningDelegate <NSObject> @optional //present時調用,返回一個實現了不可交互轉場動畫協議的代理 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source; //dismiss時調用,返回一個實現了不可交互轉場動畫協議的代理 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed; //presnt過程當中交互時調用,返回一個實現了可交互的轉場動畫協議的代理 - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator; //dismiss過程當中交互時調用,返回一個實現了可交互的轉場動畫協議的代理 - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator; //返回新的模態彈框控制器(這個是對模態風格進行自定義時調用,後面會說到) - (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(nullable UIViewController *)presenting sourceViewController:(UIViewController *)source API_AVAILABLE(ios(8.0)); @end
(2)而後在上面的協議方法中返回一個實現了UIViewControllerAnimatedTransitioning協議的代理,在這個代理的協議方法中能夠真正重寫轉場動畫了,協議以下:
@protocol UIViewControllerAnimatedTransitioning <NSObject> //動畫執行時間 - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext; //自定義轉場動畫 - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext; @optional
(3)自定義轉場動畫實現以下【注意:Singleton單例類和UIView+Extesion分類須要本身去拷貝引入】
#import <UIKit/UIKit.h> #import "Singleton.h" @interface TransitionDelegate : NSObject<UIViewControllerTransitioningDelegate> SingletonH(TransitionDelegate); @end
#import "TransitionDelegate.h" #import "CustomAnimationTransition.h" @implementation TransitionDelegate SingletonM(TransitionDelegate); #pragma mark - <UIViewControllerTransitioningDelegate> //展現的動畫 - (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { CustomAnimationTransition *animation = [[CustomAnimationTransition alloc]init]; animation.presented = YES; return animation; } //關閉的動畫 - (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed { CustomAnimationTransition *animation = [[CustomAnimationTransition alloc]init]; animation.presented = NO; return animation; } @end
#import <UIKit/UIKit.h> @interface CustomAnimationTransition : NSObject<UIViewControllerAnimatedTransitioning> //判斷是present仍是dismiss, YES:present NO:dismisss @property (assign,nonatomic)BOOL presented; @end
//設置過渡動畫(modal和dismiss的動畫都須要在這裏處理) - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { // UITransitionContextToViewKey, // UITransitionContextFromViewKey. //出來的動畫 if (self.presented) { //獲取並添加轉場視圖 UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey]; [transitionContext.containerView addSubview:toView]; //設置動畫從上往下出來 toView.y = -toView.height; [UIView animateWithDuration:duration animations:^{ toView.y = 0; } completion:^(BOOL finished) { //移除視圖 BOOL cancle = [transitionContext transitionWasCancelled]; if (cancle) { [toView removeFromSuperview]; } //動畫完成後,視圖上的事件才能處理 [transitionContext completeTransition:!cancle]; }]; } //銷燬的動畫 else { //獲取轉場視圖 UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; [UIView animateWithDuration:duration animations:^{ fromView.y = -fromView.height; } completion:^(BOOL finished) { //移除視圖 BOOL cancle = [transitionContext transitionWasCancelled]; if (!cancle) { [fromView removeFromSuperview]; } //動畫完成後,視圖上的事件才能處理 [transitionContext completeTransition:!cancle]; }]; } }
//present UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[[SecondViewController alloc] init]]; nav.transitioningDelegate = [TransitionDelegate sharedTransitionDelegate];//自定義轉場動畫 nav.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nav animated:YES completion:nil];
(4)咱們已經實現了一個簡單的自定義模態不可交互的轉場動畫,其實,在模態控制器的時候,咱們還能夠自定義可交互的轉場動畫以及設置自定義的模態風格。可交互的轉場動畫一下子再討論,先來討論一下模態風格,系統在iOS13以前默認都是滿屏模式的UIModalPresentationFullScreen,可是iOS13以後,默認是UIModalPresentationPageSheet。系統提供的模態風格以下:
//模態風格枚舉 typedef NS_ENUM(NSInteger, UIModalPresentationStyle) { UIModalPresentationFullScreen = 0, UIModalPresentationPageSheet , UIModalPresentationFormSheet , UIModalPresentationCurrentContext , UIModalPresentationCustom , //自定義 UIModalPresentationOverFullScreen , UIModalPresentationOverCurrentContext ), UIModalPresentationPopover , UIModalPresentationBlurOverFullScreen , UIModalPresentationNone, UIModalPresentationAutomatic , };
(5)從上面的枚舉能夠看到,系統是支持咱們實現本身的風格的,也就是自定義。在實現自定義以前,必定得知道UIPresentationController這個類,這個是彈出框控件,模態的控制器都是由它進行管理,主要代碼以下:
//重寫此方法能夠在彈框即將顯示時執行所須要的操做 - (void)presentationTransitionWillBegin;
//重寫此方法能夠在彈框顯示完畢時執行所須要的操做 - (void)presentationTransitionDidEnd:(BOOL)completed;
//重寫此方法能夠在彈框即將消失時執行所須要的操做 - (void)dismissalTransitionWillBegin;
//重寫此方法能夠在彈框消失以後執行所須要的操做 - (void)dismissalTransitionDidEnd:(BOOL)completed;
//重寫決定了彈出框的frame - (CGRect)frameOfPresentedViewInContainerView;
//重寫對containerView進行佈局 - (void)containerViewWillLayoutSubviews; - (void)containerViewDidLayoutSubviews;
//初始化方法 - (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController;
(6)額外再提一個知識點,由於一下子在自定義模態風格時會涉及到。在本文開篇結構圖中介紹了建立的轉場動畫都是在轉場動畫上下文UIViewControllerContextTransitioning協議中完成的,那麼這個轉場動畫的執行是誰管理呢?看結構圖以下,沒錯,是由UIViewControllerTransitionCoordinator這個代理協調器在協調器上下文中完成的,系統給UIViewController提供了一個分類,這個分類持有這個代理協調器,經過這個代理協調器能夠拿到執行轉場動畫的方法。最終,咱們能夠本身添加一些操做與轉場動畫同步執行。
UIViewControllerContextTransitioning協議核心內容
@protocol UIViewControllerTransitionCoordinatorContext <NSObject> // 執行的屬性 @property(nonatomic, readonly, getter=isAnimated) BOOL animated; @property(nonatomic, readonly) UIModalPresentationStyle presentationStyle; @property(nonatomic, readonly) NSTimeInterval transitionDuration; @property(nonatomic, readonly) UIView *containerView; @property(nonatomic, readonly) CGAffineTransform targetTransform // 參與控制器 // UITransitionContextToViewControllerKey、UITransitionContextFromViewControllerKey - (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key; // 參與的視圖 // UITransitionContextToViewKey、UITransitionContextFromViewKey - (nullable __kindof UIView *)viewForKey:(UITransitionContextViewKey)key API_AVAILABLE(ios(8.0)); @end
UIViewControllerTransitionCoordinator協議核心內容
// 與動畫控制器中的轉場動畫同步,執行其餘動畫 - (BOOL)animateAlongsideTransition:(void (^ __nullable)(id <UIViewControllerTransitionCoordinatorContext>context))animation completion:(void (^ __nullable)(id <UIViewControllerTransitionCoordinatorContext>context))completion; // 與動畫控制器中的轉場動畫同步,在指定的視圖內執行動畫 - (BOOL)animateAlongsideTransitionInView:(nullable UIView *)view animation:(void (^ __nullable)(id <UIViewControllerTransitionCoordinatorContext>context))animation completion:(void (^ __nullable)(id <UIViewControllerTransitionCoordinatorContext>context))completion;
UIViewController(UIViewControllerTransitionCoordinator) 分類核心內容
//持有轉場動畫執行協調器 @interface UIViewController(UIViewControllerTransitionCoordinator) @property(nonatomic, readonly, nullable) id <UIViewControllerTransitionCoordinator> transitionCoordinator; @end
(7)自定義模態風格實現以下【注意:Singleton單例類和UIView+Extesion分類須要本身去拷貝引入】
#import <UIKit/UIKit.h> #import "Singleton.h" @interface TransitionDelegate : NSObject<UIViewControllerTransitioningDelegate> SingletonH(TransitionDelegate); @end
#import "TransitionDelegate.h" #import "CustomPresentationController.h" #import "CustomAnimationTransition.h" @implementation TransitionDelegate SingletonM(TransitionDelegate); #pragma mark - <UIViewControllerTransitioningDelegate> //返回模態風格 -(UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source { return [[CustomPresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting]; } //展現的動畫 - (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { CustomAnimationTransition *animation = [[CustomAnimationTransition alloc]init]; animation.presented = YES; return animation; } //關閉的動畫 - (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed { CustomAnimationTransition *animation = [[CustomAnimationTransition alloc]init]; animation.presented = NO; return animation; } @end
#import <UIKit/UIKit.h> @interface CustomAnimationTransition : NSObject<UIViewControllerAnimatedTransitioning> //判斷是present仍是dismiss, YES:present NO:dismisss @property (assign,nonatomic)BOOL presented; @end
#import "CustomAnimationTransition.h" #import "UIView+Extension.h" const CGFloat duration = 0.5f; @implementation CustomAnimationTransition #pragma mark -<UIViewControllerAnimatedTransitioning> //動畫時間 - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return duration; } //設置過渡動畫(modal和dismiss的動畫都須要在這裏處理) - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { // UITransitionContextToViewKey, // UITransitionContextFromViewKey.
//返現此處並無添加toView到containerView中以及從containerView中移除toView,與上面的有區別。
//我把添加和移除toView的操做放到了下面的自定義的模態風格類中完成的
//出來的動畫 if (self.presented) { UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey]; //設置動畫從上往下出來 toView.y = -toView.height; [UIView animateWithDuration:duration animations:^{ toView.y = 0; } completion:^(BOOL finished) { //動畫完成後,視圖上的事件才能處理 [transitionContext completeTransition:YES]; }]; } //銷燬的動畫 else { [UIView animateWithDuration:duration animations:^{ UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; fromView.y = -fromView.height; } completion:^(BOOL finished) { //動畫完成後,視圖上的事件才能處理 [transitionContext completeTransition:YES]; }]; } }
#import "CustomPresentationController.h" @implementation CustomPresentationController //能夠改變被模態的控制器視圖的尺寸 - (CGRect)frameOfPresentedViewInContainerView {
//CGRectInset: 在containerView的frame基礎上,將width減少100,將height減少200 //containerView是容納presentedView的一個容器 return CGRectInset(self.containerView.bounds, 50, 100); } //將上面重置的frame完成佈局 - (void)containerViewDidLayoutSubviews { self.presentedView.frame = self.frameOfPresentedViewInContainerView; [super containerViewDidLayoutSubviews]; } //過渡即將展現時的處理 //這個過程能夠改變視圖屬性、或者添加視圖等 - (void)presentationTransitionWillBegin { self.presentedView.frame = self.containerView.frame; [self.containerView addSubview:self.presentedView]; } //過渡展現完成 //作清理工做 - (void)presentationTransitionDidEnd:(BOOL)completed { if (!completed) { [self.presentedView removeFromSuperview]; } } //過渡即將消失時的處理 //這個過程能夠改變視圖屬性等 - (void)dismissalTransitionWillBegin { //例如改變透明度,與轉場控制器中的轉場動畫同步執行 [self.presentingViewController.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) { self.presentedView.alpha = 0.f; } completion:nil]; } //過渡消失完成 //作清理工做 - (void)dismissalTransitionDidEnd:(BOOL)completed { if (completed) { [self.presentedView removeFromSuperview]; } } @end
//present UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[[SecondViewController alloc] init]]; nav.transitioningDelegate = [TransitionDelegate sharedTransitionDelegate];//自定義轉場動畫 nav.modalPresentationStyle = UIModalPresentationCustom; //自定義模態風格 [self presentViewController:nav animated:YES completion:nil];
(8)自定義模態轉場動畫和自定義模態風格咱們都實現完了,可是上面的動畫過程當中都是不可交互的,那麼要想實現可交互的動畫該怎麼作呢?如上面所說的,在dismiss時返回一個實現了UIViewControllerInteractiveTransitioning協議的代理或者直接是原生類UIPercentDrivenInteractiveTransition對象。其中,UIPercentDrivenInteractiveTransition是系統封裝好了百分比驅動,用起來很簡單,那麼真正的實現原理仍是咱們去實現一下。下面我們來實現導航模式的交互效果,以下:
#import <UIKit/UIKit.h> #import "Singleton.h"
@interface TransitioningDelegate : NSObject<UIViewControllerTransitioningDelegate> SingletonH(TransitioningDelegate); @end
#import "TransitioningDelegate.h" #import "CustomAnimationTransition.h" #import "CustomInteractiveTransition.h" @implementation TransitioningDelegate SingletonM(TransitioningDelegate); #pragma mark - UIViewControllerTransitioningDelegate //present - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { //這裏仍是採用自定義的轉場動畫方式進行present,使其present時從屏幕右側滑入
CustomAnimationTransition *animation = [[CustomAnimationTransition alloc]init]; animation.presented = YES; return animation; } //dismiss,必須重寫 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed { //這裏採用自定義的轉場動畫覆蓋系統的dismiss效果,在dismiss時,因爲自定義了交互動畫,因此係統本身的dismiss動畫不會執行
CustomAnimationTransition *animation = [[CustomAnimationTransition alloc] init]; animation.presented = NO; return animation; } //將要dismiss時的交互行爲,必須重寫 - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator { CustomInteractiveTransition *animation = [[CustomInteractiveTransition alloc] init]; return animation; } @end
#import <UIKit/UIKit.h> @interface CustomAnimationTransition : NSObject<UIViewControllerAnimatedTransitioning> //判斷是present仍是dismiss, YES:present NO:dismisss @property (assign,nonatomic)BOOL presented; @end
#import "CustomAnimationTransition.h" #import "UIView+Extension.h" const CGFloat duration = 0.5f; @implementation CustomAnimationTransition #pragma mark -<UIViewControllerAnimatedTransitioning> //動畫時間 - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return duration; } //設置過渡動畫(modal和dismiss的動畫都須要在這裏處理) - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { // UITransitionContextToViewKey, // UITransitionContextFromViewKey. //出來的動畫 if (self.presented) { //獲取並添加轉場視圖 UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey]; [transitionContext.containerView addSubview:toView]; //設置動畫從右往左出來 toView.x = toView.width; [UIView animateWithDuration:duration animations:^{ toView.x = 0; } completion:^(BOOL finished) { //移除視圖 BOOL cancle = [transitionContext transitionWasCancelled]; if (cancle) { [toView removeFromSuperview]; } //動畫完成後,視圖上的事件才能處理 [transitionContext completeTransition:!cancle]; }]; } //銷燬的動畫 else { //不作處理,而是交給自定義的交互動畫去完成 } } @end
#import <UIKit/UIKit.h> #import "Singleton.h" @interface CustomInteractiveTransition : NSObject<UIViewControllerInteractiveTransitioning> SingletonH(CustomInteractiveTransition); //採用單例的方式主要是爲了保存交互上下文 //動畫進度更新 -(void)updateAnimationProgress:(CGFloat)progress; //動畫完成 -(void)finish; //動畫取消 -(void)cancel; @end
#import "CustomInteractiveTransition.h" #import "UIView+Extension.h" @interface CustomInteractiveTransition () @property (nonatomic, strong) id<UIViewControllerContextTransitioning> context; @end @implementation CustomInteractiveTransition SingletonM(CustomInteractiveTransition); #pragma mark - UIViewControllerInteractiveTransitioning //開始交互時調用 - (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext { //保存上下文 self.context = transitionContext; //更改視圖層級 UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey]; UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; [transitionContext.containerView insertSubview:toView belowSubview:fromView]; } //動畫進度更新 -(void)updateAnimationProgress:(CGFloat)progress { UIView *fromView = [self.context viewForKey:UITransitionContextFromViewKey]; fromView.x = self.context.containerView.width * progress; } //動畫完成 -(void)finish {
UIView *fromView = [self.context viewForKey:UITransitionContextFromViewKey]; [UIView animateWithDuration:0.2 animations:^{ fromView.x += self.context.containerView.width; } completion:^(BOOL finished) {
[fromView removeFromSuperView];
[self.context completeTransition:finished]; }]; } //動畫取消 -(void)cancel {
UIView *fromView = [self.context viewForKey:UITransitionContextFromViewKey]; [UIView animateWithDuration:0.2 animations:^{ fromView.x = 0; } completion:^(BOOL finished) { [fromView removeFromSuperView];
[self.context cancelInteractiveTransition]; }]; } @end
#import "SecondViewController.h" #import "CustomInteractiveTransition.h" @interface SecondViewController () @end @implementation SecondViewController - (void)viewDidLoad { [super viewDidLoad]; self.title = @"secondVc"; self.view.backgroundColor = [UIColor redColor]; [self.view addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]]; } -(void)pan:(UIPanGestureRecognizer *)pan { CGPoint translatedPoint = [pan translationInView:self.view]; CGFloat progress = translatedPoint.x / [UIScreen mainScreen].bounds.size.width; if (progress < 0) { return; } //拖拽的距離進度比 progress = fabs(progress); CustomInteractiveTransition *transition = [[CustomInteractiveTransition alloc] init]; switch (pan.state) { case UIGestureRecognizerStateBegan: [self dismissViewControllerAnimated:YES completion:nil]; break; case UIGestureRecognizerStateChanged: [transition updateAnimationProgress:progress]; break; case UIGestureRecognizerStateEnded: { if (progress > 0.5) { [transition finish]; }else{ [transition cancel]; } break; } default: break; } } @end
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[[SecondViewController alloc] init]]; nav.transitioningDelegate = [TransitioningDelegate sharedTransitioningDelegate];//自定義可交互轉場動畫 nav.modalPresentationStyle = UIModalPresentationFullScreen; //系統模態風格 [self presentViewController:nav animated:YES completion:nil];
5、實現一個自定義的導航動畫
一、重寫導航控制器的協議,返回自定義的導航轉場動畫,動畫實現的方式和modal思想一致,就不截圖實現了,重寫的核心協議以下:
//重寫導航控制器協議 @protocol UINavigationControllerDelegate <NSObject> @optional ................ //返回一個實現了自定義交互動畫的對象 - (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController; //返回一個實現了普通動畫的對象 - (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC; @end
二、 如今就來自定義一個導航轉場動畫,步驟以下:
#import <UIKit/UIKit.h> #import "Singleton.h" NS_ASSUME_NONNULL_BEGIN @interface CustomNavigationTransition : NSObject<UINavigationControllerDelegate> SingletonH(CustomNavigationTransition); @end NS_ASSUME_NONNULL_END
#import "CustomNavigationTransition.h" #import "CustomNavigationAnimation.h" @implementation CustomNavigationTransition SingletonM(CustomNavigationTransition); #pragma mark - UINavigationControllerDelegate //返回一個實現了普通動畫的對象 - (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { CustomNavigationAnimation *animation = [[CustomNavigationAnimation alloc] init]; animation.operation = operation; return animation; } @end
#import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @interface CustomNavigationAnimation : NSObject<UIViewControllerAnimatedTransitioning> @property (nonatomic, assign) UINavigationControllerOperation operation; @end NS_ASSUME_NONNULL_END
#import "CustomNavigationAnimation.h" #import "UIView+Extension.h" const CGFloat _duration = 0.5f; @implementation CustomNavigationAnimation #pragma mark -<UIViewControllerAnimatedTransitioning> //動畫時間 - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return _duration; } //設置過渡動畫(modal和dismiss的動畫都須要在這裏處理) - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { // UITransitionContextToViewKey, // UITransitionContextFromViewKey. //push if (self.operation == UINavigationControllerOperationPush) { //轉場視圖 UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey]; [transitionContext.containerView addSubview:toView]; //設置動畫從右上push進來 toView.x = toView.width; toView.y = -toView.height; [UIView animateWithDuration:_duration animations:^{ toView.x = 0; toView.y = 0; } completion:^(BOOL finished) { //移除視圖 BOOL cancle = [transitionContext transitionWasCancelled]; if (cancle) { [toView removeFromSuperview]; } //動畫完成後,視圖上的事件才能處理 [transitionContext completeTransition:!cancle]; }]; } //pop else if(self.operation == UINavigationControllerOperationPop) { //轉場視圖,更改層級關係 UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey]; [transitionContext.containerView insertSubview:toView belowSubview:fromView]; [UIView animateWithDuration:_duration animations:^{ //pop返回右上 fromView.x = fromView.width; fromView.y = -fromView.height; } completion:^(BOOL finished) { //移除視圖 BOOL cancle = [transitionContext transitionWasCancelled]; if (!cancle) { [fromView removeFromSuperview]; } //動畫完成後,視圖上的事件才能處理 [transitionContext completeTransition:!cancle]; }]; } } @end
//push ThirdViewController *vc = [[ThirdViewController alloc] init]; self.navigationController.delegate = [CustomNavigationTransition sharedCustomNavigationTransition]; [self.navigationController pushViewController:vc animated:YES];
6、實現一個自定義的標籤欄切換動畫
一、重寫標籤欄控制器的協議,返回自定義的標籤欄切換轉場動畫,動畫實現的方式和modal思想一致,就不截圖實現了,重寫的核心協議以下:
//重寫標籤欄協議 @protocol UITabBarControllerDelegate <NSObject> @optional ...................... //返回一個實現了可交互的標籤欄轉場動畫對象 - (nullable id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController; //返回一個實現了普通的標籤欄轉場動畫對象 - (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC; @end
二、 如今就來自定義一個標籤轉場動畫,步驟以下:
//注意:我使用StoryBoard搭建的界面 #import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @interface CustomTabBarViewController : UITabBarController @end NS_ASSUME_NONNULL_END
#import "CustomTabBarViewController.h" #import "CustomTabbarTransition.h" @interface CustomTabBarViewController () @end @implementation CustomTabBarViewController -(instancetype)initWithCoder:(NSCoder *)coder { if (self = [super initWithCoder:coder]) { //設置代理 self.delegate = [CustomTabbarTransition sharedCustomTabbarTransition]; } return self; } - (void)viewDidLoad { [super viewDidLoad]; } @end
#import <UIKit/UIKit.h> #import "Singleton.h" NS_ASSUME_NONNULL_BEGIN @interface CustomTabbarTransition : NSObject<UITabBarControllerDelegate> SingletonH(CustomTabbarTransition); @end NS_ASSUME_NONNULL_END
#import "CustomTabbarTransition.h" #import "CustomTabbarAnimation.h" @implementation CustomTabbarTransition SingletonM(CustomTabbarTransition); #pragma mark - UITabBarControllerDelegate //返回一個實現了普通動畫的對象 - (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { CustomTabbarAnimation *animation = [[CustomTabbarAnimation alloc] init]; return animation; } @end
#import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @interface CustomTabbarAnimation : NSObject<UIViewControllerAnimatedTransitioning> @end NS_ASSUME_NONNULL_END
#import "CustomTabbarAnimation.h" #import "UIView+Extension.h" const CGFloat _Duration = 0.5f; @implementation CustomTabbarAnimation #pragma mark -<UIViewControllerAnimatedTransitioning> //動畫時間 - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return _Duration; } //設置過渡動畫(modal和dismiss的動畫都須要在這裏處理) - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { // UITransitionContextToViewKey, // UITransitionContextFromViewKey. //轉場視圖 UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey]; UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; [transitionContext.containerView addSubview:toView]; [transitionContext.containerView sendSubviewToBack:toView]; //尺寸變化,從原尺寸縮小到點 CGRect finalFrame = CGRectInset(transitionContext.containerView.frame, transitionContext.containerView.frame.size.width/2, transitionContext.containerView.frame.size.height/2); [UIView animateWithDuration:_Duration animations:^{ fromView.frame = finalFrame; } completion:^(BOOL finished) { //移除視圖 BOOL cancle = [transitionContext transitionWasCancelled]; if (!cancle) { [fromView removeFromSuperview]; } //動畫完成後,視圖上的事件才能處理 [transitionContext completeTransition:!cancle]; }]; } @end
7、總結
好了,這三種經常使用的轉場動畫都進行了自定義,固然至於後兩種的交互轉場動畫跟modal實現原理同樣,就不介紹了。借用和修改別人的一個圖,作個總結吧,以下: