iOS过场动画调研笔记

前言

因项目须要,近期一段时间都在调研iOS的过场动画。对于我来说这是一个之前没有太涉及的领域,所以有必要把调研的过程和自己的一些理解纪录下来

为什么要自己定义过场动画?

假设大家有关注Material Design和近期一些知名App(如快的、一号专车等)的界面设计和交互的变化,就会发现一种新的趋势:平滑的页面过渡。目的旨在于让用户尽量少地感觉到页面之间生硬的切换,从而使App的体验更加流畅。而iOS原生的两种经常使用过场:Push/Pop和Present,和眼下流行的趋势显然是不太符合的。所以自己定义过场动画的意义就体现出来了。

Transition-iOS的自己定义过场

简单介绍

由于之前有博客对自己定义过场做了很详细的介绍,我就不赘述了,详细參照这里 iOS7之定制View Controller切换效果 (PS:感谢作者)。作者的demo我也有下载看过。他是为每一个过场动画封装了单独的类,然后在UIViewController中实现过场切换的代理,在代理中返回对应的动画效果。对于为过场动画封装单独的类这点我是很赞同的,可是在UIViewController中实现过场切换的代理这一点我认为不是特别理想,所以后来我的实现做了改动,终于的效果是在UIViewController中仅仅须要调用一个接口。就能够实现自己定义过场的效果。

我的设计

分析

首先,我封装了一个单例模式MBTransition基类,使用单例模式的原因有两个:

  1. 在一个App中,同一时候存在的过场仅仅会有一个。
  2. 实现成单例之后过场对象就不须要依赖于某个UIViewController。

然后.m文件里为这个类实现过场动画的几个代理

#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文件里加入一个变量来保存过场动画持续的时间:

@property (nonatomic, assign) NSTimeInterval duration;

接下来我们分析一下 (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext 代理中我们做的一些事情:

  1. 通过 transitionContext 获取到过场时切换的两个UIViewController

    // 这里的 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

原文地址:https://www.cnblogs.com/llguanli/p/7237928.html