姬長信(Redy) 大佬早!

源码

首页 » 归档 » 源码 » iOS中应该知道的自界说各种Controller的转场过渡动画-ios学习从入门到精通尽在姬长信

iOS中应该知道的自界说各种Controller的转场过渡动画-ios学习从入门到精通尽在姬长信

分享最热门的ios资讯

授权转载,作者:ZeroJ(Github

前言

正如标题所示,iOS开发中,自界说转场的过渡动画确实是必须要了解的,在iOS7之后实现也是很简单的。如果会使用它,可以实现很多对照实用的功能。比如:

如果觉得系统的UIAlertController不能满足需求, 那么你可以使用自界说转场过渡动画的方式来实现弹出自界说的控制器(同时实现对照实用的动画效果)。

系统默认的present是从下方弹出控制器, 可以通过自界说转场过渡动画的方式来自界说切换页面的动画

倒霉用手势实现tabbarController滑动切换页面

倒霉用手势实现navigationController全屏返回的功能

......

本篇中首先介绍自界说present/dismiss的转场动画的方式 Demo地址

最终效果如下

1271831-beff9e5ccbe46cd1.gif

1271831-c6c477e4f72e73ca.gif

一、在iOS7以后Apple提供了很方便的接口来实现自界说转场动画, 使用起来很是简单方便,在实现过程中会接触到三个工具。

  • Delegate: 一个继承自NSObject的署理, 而且需要遵守相关的协议, 用来指定动画中需要的其他两个工具(下面提到的两个), 需要遵守相关的协议如下

  • (UIViewControllerTransitioningDelegate -- 自界说present/dismiss的时候)

  • UINavigationControllerDelegate --- 自界说navigationController转场动画的时候

  • UITabBarControllerDelegate --- 自界说tabbarController转场动画的时候

  • ......

  • UIViewControllerAnimatedTransitioning: 这个协议中提供了接口, 遵守这个协议的工具实现动画的具体内容

  • UIViewControllerInteractiveTransitioning: 这个协议中提供了手势交互动画的接口, 不过, 我们大多都是使用它的一个子类UIPercentDrivenInteractiveTransition来更简单的实现手势交互动画

二、了解UIViewControllerTransitioningDelegate

这个署理需要提供两种类型的工具给系统来实现自界说动画, 如果没有提供, 将会使用系统默认的动画效果

第一种类型工具是遵守UIViewControllerAnimatedTransitioning协议的工具

// 自界说present弹出控制器时的动画需要提供的遵守UIViewControllerAnimatedTransitioning工具
optional public func animationController(forPresentedController presented: UIViewController, presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning?
// 自界说dismiss移除控制器时的动画需要提供的遵守UIViewControllerAnimatedTransitioning工具
@available(iOS 2.0, *)
optional public func animationController(forDismissedController dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?

第二种类型工具是遵守UIViewControllerInteractiveTransitioning的工具

// 自界说交互动画(手势, 或者重力感应...)需要提供的遵守UIViewControllerInteractiveTransitioning工具
optional public func interactionController(forDismissal animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

三、了解UIViewControllerAnimatedTransitioning

这个协议是上面提到的署理来获取到具体的动画操作的

遵守这个协议的工具来只需要实现两个必须的要领

// 通过这个要领获取到动画执行的时间
  public func transitionDuration(_ transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
// 在这个要领中通过获取到源控制器和目标控制器等来执行动画
  // This method can only  be a nop if the transition is interactive and not a percentDriven interactive transition.
  public func animateTransition(_ transitionContext: UIViewControllerContextTransitioning)

四、了解UIPercentDrivenInteractiveTransition

  • UIPercentDrivenInteractiveTransition 是实现了

UIViewControllerInteractiveTransitioning这个协议的

我们使用UIPercentDrivenInteractiveTransition可以简单的

通过调用提供的几个函数来执行具体的动画

(会调用UIViewControllerAnimatedTransitioning里面实现的动画)

  • 一般可以通过继承(也可不继承)它来实现可交互动画

  • 在子类中通过添加手势(或者其他方式)到相应的view上面, 在手势的响应要领

中凭据差别的手势状态来进行差别的交互动画的操作, 一般使用到如下三个函数

// 更面目目样动画进度
 public func update(_ percentComplete: CGFloat)
// 取消交互动画
  public func cancel()
// 完成交互动画
  public func finish()

五、了解UIViewControllerContextTransitioning

在UIViewControllerAnimatedTransitioning协议的

实现具体动画的函数中

func animateTransition(_ transitionContext:UIViewControllerContextTransitioning)

我们会接触到UIViewControllerContextTransitioning

这个接口用来提供切换上下文给开发者使用,包罗了从哪个VC到哪个VC等

各类信息, 我们可以很方便的获取到源控制器和目标控制器...很多我们需要的属性

* 使用viewControllerForKey: 获取到源控制器和目标控制器

* 使用containerView获取到当前的containerView, 将要执行动画的view都在这个containerView上进行

* 使用viewForKey: 获取到将要添加或者移除的view(一般是控制器的view)

* 使用finalFrameForViewController:获取到将要添加或者移除的view的最终frame

* 注意 'from' -> 指的的当前正在屏幕上显示的控制器(present和dismiss的时候是纷歧样的)

六、自界说present/dismiss动画的系统调用过程

  • 首先设置controller的署理transitioningDelegate为我们自界说的, 如果我们的署理里面没有提供上面所需要的工具, 那么将会使用系统默认的

prenting动画执行过程

  • UIKit首先会调用署理的

animationControllerForPresentedController:presentingController:sourceController:要领取得自界说的动画工具

  • UIKit接着调用署理的 interactionControllerForPresentation: 要领看是否支持交互性动画, 如果返回nil表示不支持

  • UIKit接着调用署理的 transitionDuration: 要领获取动画执行的时间

  • 如果是不可交互的动画UIKit会调用署理的animateTransition:要领来执行真正的动画,

  • 如果是可交互的动画, UIKit会调用署理的startInteractiveTransition:要领开始动画

  • 接着是执行动画的操作, 而且等候署理调用completeTransition:结束动画(所以我们一定需要在动画执行完毕后调用这个要领, 告诉系统我们的动画执行完毕或者中途取消了)

dismiss动画执行过程和上面只有第一步和第二步伐用的署理要领纷歧样

例如第一步伐用(animationControllerForDismissedController:), 其他是相同的过程

七、下面以自界说present/dismiss动画过程示例上面提到的各种用法(注意: 使用的swift3.0 xcode8, 如果是使用oc或者swift低版本的朋友请对应转换相应的语法)

首先面目目样建一个CustomAnimator继承自NSObject, 而且遵守UIViewControllerAnimatedTransitioning协议, 来处理动画的实现

class CustomAnimator:NSObject, UIViewControllerAnimatedTransitioning {

然后实现这个协议中必须的两个要领来实现具体的动画

class CustomAnimator:NSObject, UIViewControllerAnimatedTransitioning {
  let duration = 0.35
// 返回动画时间
  func transitionDuration(_ transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
      return duration
  }
// 处理具体动画, 通过transitionContext可以获取到很多我们需要的乳
  func animateTransition(_ transitionContext: UIViewControllerContextTransitioning) {
      // fromVc 总是获取到正在显示在屏幕上的Controller
      let fromVc = transitionContext.viewController(forKey: UITransitionContextFromViewControllerKey)!
      // toVc 总是获取到将要显示的controller
      let toVc = transitionContext.viewController(forKey: UITransitionContextToViewControllerKey)!
      let containView = transitionContext.containerView()
      let toView: UIView
      let fromView: UIView
      if transitionContext.responds(to:NSSelectorFromString("viewForKey:")) {
          // 通过这种要领获取到view纷歧定是对应controller.view
          toView = transitionContext.view(forKey: UITransitionContextToViewKey)!
          fromView = transitionContext.view(forKey: UITransitionContextFromViewKey)!
      } else { // Apple文档中提到不要直接使用这种要领来获取fromView和toView
          toView = toVc.view
          fromView = fromVc.view
      }
      //  添加toview到最上面(fromView是当前显示在屏幕上的view不用添加)
      containView.addSubview(toView)
      // 最终显示在屏幕上的controller的frame
      let visibleFrame = transitionContext.initialFrame(for: fromVc)
      // 隐藏在右边的controller的frame
      let rightHiddenFrame = CGRect(origin: CGPoint(x: visibleFrame.width, y: visibleFrame.origin.y) , size: visibleFrame.size)
      // 隐藏在左边的controller的frame
      let leftHiddenFrame = CGRect(origin: CGPoint(x: -visibleFrame.width, y: visibleFrame.origin.y) , size: visibleFrame.size)
      // toVc.presentingViewController --> 弹出toVc的controller
      // 所以如果是present的时候  == fromVc
      // 或者可以使用 fromVc.presentedViewController == toVc
      let isPresenting = toVc.presentingViewController == fromVc
      if isPresenting {// present Vc左移
          toView.frame = rightHiddenFrame
          fromView.frame = visibleFrame
      } else {// dismiss Vc右移
          fromView.frame = visibleFrame
          toView.frame = leftHiddenFrame
          // 有时需要将toView添加到fromView的下面便于执行动画
//            containView.insertSubview(toView, belowSubview: fromView)
      }
      UIView.animate(withDuration: duration, delay: 0.0, options: [.curveLinear], animations: {
          if isPresenting {
              toView.frame = visibleFrame
              fromView.frame = leftHiddenFrame
          } else {
              fromView.frame = rightHiddenFrame
              toView.frame = visibleFrame
          }
      }) { (_) in
          let cancelled = transitionContext.transitionWasCancelled()
          if cancelled {
              // 如果中途取消了就移除toView(可交互的时候会发生)
              toView.removeFromSuperview()
          }
          // 通知系统动画是否完成或者取消了
          transitionContext.completeTransition(!cancelled)
      }
  }
}

接着面目目样建一个CustomDelegate继承自NSObject,而且遵守

UIViewControllerTransitioningDelegate协议, 来实现动画的署理的工作

class CustomDelegate: NSObject, UIViewControllerTransitioningDelegate

接着实现需要自界说的相应的要领, 而且返回所需的执行工具

class CustomDelegate: NSObject, UIViewControllerTransitioningDelegate {
  private lazy var customAnimator = CustomAnimator()
  // 提供present的时候使用到的动画执行工具
  func animationController(forPresentedController presented: UIViewController, presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
      return customAnimator
  }
  // 提供dismiss的时候使用到的动画执行工具
  func animationController(forDismissedController dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
      return customAnimator
  }
}

到这里为止就已经实现了自界说的不可交互的转场动画, 可以使用了, 效果和我们图片示例的一样

class Test1Controller: UIViewController {
// 动画署理
  let deletage = CustomDelegate()
  @IBAction func present(_ sender: UIButton) {
      let testVc = TestController()
      testVc.view.backgroundColor = UIColor.red()
      testVc.modalPresentationStyle = .fullScreen
      // 因为transitioningDelegate是weak 所以这里不能使用局部变量 CustomDelegate()
//        testVc.transitioningDelegate = CustomDelegate()
    // 设置署理为我们自界说的
      testVc.transitioningDelegate = deletage
// 弹出控制器
      present(testVc, animated: true, completion: nil)
  }

然后我们添加可交互的工具, 首先面目目样建 Interactive:继承自

UIPercentDrivenInteractiveTransition

class Interactive: UIPercentDrivenInteractiveTransition

接着添加手势, 而且在手势处理过程中凭据差别的手势状态执行差别的操作

class Interactive: UIPercentDrivenInteractiveTransition {
// pan手势
  lazy var panGesture: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action:  #selector(self.handlePan(gesture:)))
// 用于添加手势
  var containerView: UIView!
// 将要被dismiss的控制器, 在动画的delegate中传入
  var dismissedVc: UIViewController! = nil {
      didSet {
          containerView = dismissedVc.view
          containerView.addGestureRecognizer(panGesture)
      }
  }
// 是否执行交互动画
  var isInteracting = false
  override init() {
      super.init()
  }
  // 处理手势
  func handlePan(gesture: UIPanGestureRecognizer) {
      //动画是否完成或者取消
      func finishOrCancel() {
          let translation = gesture.translation(in: containerView)
          let percent = translation.x / containerView.bounds.width
          let velocityX = gesture.velocity(in: containerView).x
          let isFinished: Bool
          if velocityX <= 0 {
              isFinished = false
          } else if velocityX > 100 {
              isFinished = true
          } else if percent > 0.3 {
              isFinished = true
          } else {
              isFinished = false
          }
          isFinished ? finish() : cancel()
      }
      switch gesture.state {
          case .began:
// 手势开始, 开启交互动画, 而且dismiss(需要设置animated: true)
              isInteracting = true
              // dimiss
              dismissedVc.dismiss(animated: true, completion: nil)
          case .changed:
// 手势改变状态, 计算动画的进度
              if isInteracting {// 开始执行交互动画的时候才设置为非nil
                  let translation = gesture.translation(in: containerView)
                  var percent = translation.x / containerView.bounds.width
                  if percent < 0 {
                      percent = 0
                  }
// 更新动画
                  update(percent)
              }
          case .cancelled:
              if isInteracting {
                  finishOrCancel()
                  isInteracting = false
              }
          case .ended:
              if isInteracting {
                  finishOrCancel()
                  isInteracting = false
              }
          default:
              break
      }
  }
}

接着在CustomDelegate里面增加实现可交互动画的执行工具和接口

// 注意在present接口里面设置了
//  interactive.dismissedVc = presented
  private lazy var interactive = Interactive()
  // 提供dismiss的时候使用到的可交互动画执行工具
  func interactionController(forDismissal animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
      // 因为执行自界说动画会先调用这个要领, 如果返回不为nil, 那么将不会执行非交互的动画!!
      // 所以isInteracting只有在手势开始的时候才被设置为true
      // 返回nil便于不是可交互的时候就直接执行不可交互的动画
      return interactive.isInteracting ? interactive : nil
  }

就是这样就实现了倒霉用手势滑动返回的可交互动画, 现在运行, 将会看到图片的示例效果, 还是很简单?!!!!

这里以自界说present/dismiss为例详细的介绍了自界说转场动画的使用, 那么到现在, 你是可以很自由的去实现各种需要的自界说动画(navigationController, tabBarController...), 而且增加各种交互动画(滑动, 捏合, 甚至设备摇晃...), 希望你会很愉快的使用:Demo地址,欢迎关注, 欢迎star。

用意志战胜身体的惰性!

(0)

本文由 姬長信 创作,文章地址:https://blog.isoyu.com/archives/580.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:7月 5, 2016 at 07:59 上午

关键词:

热评文章

发表回复

[必填]

我是人?

提交后请等待三秒以免造成未提交成功和重复