iOS過場動畫調研筆記

前言

因項目需要,近期一段時間都在調研iOS的過場動畫。對於我來講這是一個以前沒有太涉及的領域,因此有必要把調研的過程和本身的一些理解紀錄下來ios

爲何要本身定義過場動畫?

假設你們有關注Material Design和近期一些知名App(如快的、一號專車等)的界面設計和交互的變化,就會發現一種新的趨勢:平滑的頁面過渡。目的旨在於讓用戶儘可能少地感受到頁面之間生硬的切換,從而使App的體驗更加流暢。而iOS原生的兩種常用過場:Push/Pop和Present,和眼下流行的趨勢顯然是不太符合的。因此本身定義過場動畫的意義就體現出來了。程序員

Transition-iOS的本身定義過場

簡單介紹

因爲以前有博客對本身定義過場作了很詳細的介紹,我就不贅述了,詳細參照這裏 iOS7之定製View Controller切換效果 (PS:感謝做者)。做者的demo我也有下載看過。他是爲每個過場動畫封裝了單獨的類,而後在UIViewController中實現過場切換的代理,在代理中返回對應的動畫效果。對於爲過場動畫封裝單獨的類這點我是很贊同的,但是在UIViewController中實現過場切換的代理這一點我認爲不是特別理想,因此後來個人實現作了改動,終於的效果是在UIViewController中僅僅需要調用一個接口。就可以實現本身定義過場的效果。markdown

個人設計

分析

首先,我封裝了一個單例模式MBTransition基類,使用單例模式的緣由有兩個:ide

  1. 在一個App中,同一時候存在的過場僅僅會有一個。
  2. 實現成單例以後過場對象就不需要依賴於某個UIViewController。

而後.m文件裏爲這個類實現過場動畫的幾個代理post

#pragma mark UINavigationControllerDelegate methods

// Push/Pop時本身定義過場的代理
// 參數:
// navigationController:導航
// operation:導航的操做:Push/Pop/None,可以用來控制在哪一種導航的操做下使用本身定義過場
// fromVC:運行Push操做的UIViewController
// toVC:被Push的UIViewController
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                  animationControllerForOperation:(UINavigationControllerOperation)operation
                                               fromViewController:(UIViewController *)fromVC
                                                 toViewController:(UIViewController *)toVC {
    return self; //返回self表示代理由類自己實現
}

// Present時本身定義過場的代理
// 參數:
// presented:被Present的UIViewController
// presenting:正在運行Present的UIViewController
// source:發起Present的UIViewController(PS:正在運行Present和發起Present的UIViewController是有差異的,
// 假設source是某個UINavigationController下的一個UIViewController,
// 那麼presenting就是這個UINavigationController,假設source不是在類似UINavigationController或者
// UITabbarController這種控件內。那麼presenting就是source自己)
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented 
    presentingController:(UIViewController *)presenting 
    sourceController:(UIViewController *)source
{
    return self;
}

// Dismiss時本身定義過場的代理
// 參數:
// dismissed:被Dismiss掉的UIViewController
-(id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    return self;
}

#pragma mark - UIViewControllerContextTransitioning

// 實現詳細本身定義過場動畫效果的代理,這個代理也是實現動畫效果的核心
// 參數:
// transitionContext:過場時的上下文信息
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
}

// 過場動畫時間的代理
// 參數:
// transitionContext:過場時的上下文信息
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
    return self.duration;
}

經過上面幾個代理咱們可以知道:Push/Pop和Present時過場動畫的代理是不同的,因此我創建了一個過場類型的枚舉,用來控制本身定義過場在哪一種交互下可用:動畫

typedef enum TransitionType{
    TransitionTypePush, // Push/Pop過場
    TransitionTypePresent // Present過場
}TransitionType;

而後在- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext代理中咱們返回了self.duration,因此咱們需要在.h文件里加入一個變量來保存過場動畫持續的時間:ui

@property (nonatomic, assign) NSTimeInterval duration;

接下來咱們分析一下 (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext 代理中咱們作的一些事情:atom

  1. 經過 transitionContext 獲取到過場時切換的兩個UIViewControllerspa

    // 這裏的 fromVC 和 toVC 表明的是過場是由 fromVC 切換到 toVC 的。
    // 比方從A界面Push到B界面時,這裏的fromVC是A界面,toVC是B界面,而當B界面被Pop到A界面時,
    // 這裏的fromVC就是B界面。toVC就是A界面
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
  2. 經過 transitionContext 獲取到運行切換的UIView設計

    // 所有的切換動畫都是基於container來實現的
    UIView *container = [transitionContext containerView];
  3. 經過 transitionContext 獲取到過場的持續時間

    NSTimeInterval duration = [self transitionDuration:transitionContext];
  4. 最後經過 transitionContext 獲取到過場時切換的 fromVC 和 toVC 和Push/Present時保存的 fromVC 和 toVC 進行比較就可以知道眼下運行的是Push/Present仍是Pop/Dismiss,從而可以爲Push/Present和Pop/Dismiss定製不一樣的動畫效果。

    - (BOOL)isReversed:(UIViewController *)fromVC ToVC:(UIViewController *)toVC
    {
        return !([self.fromVC class] == [fromVC class] && [self.toVC class] == [toVC class]);
    }

    因此咱們需要在.h文件里加入兩個成員變量來保存Push/Present時的 fromVC 和 toVC :

    @property (nonatomic, weak) UIViewController *fromVC;
    @property (nonatomic, weak) UIViewController *toVC;
  5. 接下來就是詳細的過場動畫部分了。事實上就是結合fromVC的view、toVC的view和container作一些動畫效果,因爲跟作普通的動畫沒有什麼差異,因此這個部分我就不詳細描寫敘述了。

最後是提供給外部調用的接口,內容例如如下:

- (void)setTransitionWithFromViewController:(UIViewController *)fromVC 
    ToViewController:(UIViewController *)toVC 
    TransitionType:(TransitionType)type 
    Duration:(NSTimeInterval)duration{

    self.fromVC = fromVC;
    self.toVC = toVC;
    self.duration = duration;
    if (type == TransitionTypePush) {
        self.fromVC.navigationController.delegate = self;
    }else if (type == TransitionTypePresent){
        self.fromVC.transitioningDelegate = self;
        self.toVC.transitioningDelegate = self;
    }
}

上面代碼片斷所作的事情就是對一些參數進行保存,而後依據 TransitionType(這就是以前那個枚舉類型) 來設置對應的代理。

特色

  1. 假設要實現其它本身定義過場,僅僅需要繼承MBTransition,而後重寫 (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext 代理就能夠。
  2. 使用者僅僅需調用一個接口就能夠實現本身定義過場,減小了代碼耦合。

交互式的切換動畫

交互式動畫主要是指動畫可以跟用戶的手勢連動,這個部分我眼下尚未研究…後面有機會再補上

碰到的一些坑

  1. 當UIViewController是UITabbarController或者UINavigationController的一個childViewController時。經過 [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey][transitionContext viewControllerForKey:UITransitionContextToViewControllerKey] 拿到的UIViewController事實上是UITabbarController或者UINavigationController。因此在調用接口時,要注意傳入的 fromVC 和 toVC 事實上是這個UIViewController的UITabbarController或者UINavigationController,假設不這樣作,isReserved方法的推斷就會發生異常。
  2. 假設採用Push/Pop的方式。當fromVC屬於UITabbarController的一個childViewController,且在 toVC 上不能顯示UITabbarController的UITabBar時。UITabbarController的UITabBar會形成很大的麻煩:

    • 假設使用設置hidesBottomBarWhenPushed爲true的方式,那麼UITabBar的動畫不能定製。僅僅能是默認的從右到左和從左到右。
    • 假設使用本身定義的方式顯示和隱藏UITabBar,因爲AutoLayout的緣由。後期問題會不少其它(PS:也多是我對這種方法的研究還不夠透徹)…

    因此在這種狀況下建議使用Present的方式切換到新的界面,固然假設你們有好的解決方法也但願能分享給我,謝謝!

後話

假設需要詳細的代碼,可以經過個人微博聯繫我畫渣程序員mmoaay

相關文章
相關標籤/搜索