iOS 自定义过渡动画

iOS custom transition animation

我已经开始使用 UIViewControllerAnimatedTransitioning 协议学习自定义过渡动画。我在 youtube 上找到的大部分视频都是基于当我们有新的 ViewController 呈现圆形动画或类似动画时的流程。

我在实施过渡方式时遇到问题。大多数情况下,我需要的是类似于 facebook 应用程序 以及它们如何打开全屏图像查看器。

所以,假设我们有 VC1VC2。在 VC1 上,我们调用动作来呈现 VC2。在两个 VC 上我们都有相同的 UI 元素。在我的例子中是 UIImageView。就像您在 VC1 上单击 imageView 一样,它会打开某个对象的详细信息页面,其图像位于顶部。我想要动画,应该看起来像来自 VC1 的图像正在将帧更改为来自 VC2 的图像的最终帧,然后详细信息页面上的其他内容(如标签、按钮等)应该出现。

但是我在训练过程中遇到了一些问题
1. 首先,我不明白transitionContextcontainerView的意思。但正如我所见,它有点像过渡之间的中间状态视图。那是对的吗?但这很奇怪 对我来说,因为即使 backgroundColor 属性 也不适合 containerView.
2. 我不明白在转换过程中我到底需要什么动画,以及 containerView 子视图的结构应该是什么。在我的示例中,当呈现 VC2 时,据我所知,我需要隐藏它的所有子视图。然后将imageView从VC1动画化到VC2imageView帧,然后再次使所有子View可见。那么,在这种情况下 imageView 应该添加到 containerView 中吗?如果是这样,那么它应该是 VC1 中的实际 imageView,还是 imageView 的全新副本,具有相同的 frame/image,只是在过渡期间临时使用...

对link我examples/tutorial/code有类似动画

会有帮助

Here is link to how that works in facebook

了解自定义过渡动画

就像你从 VCA 导航到 VCB 然后

  1. 首先你需要使用UIViewControllerTransitioningDelegate.

转换委托 负责提供用于自定义转换的动画控制器。您指定的委托对象必须符合 UIViewControllerTransitioningDelegate 协议。

  1. 现在你必须使用 UIViewControllerAnimatedTransitioning

它负责 持续时间 和动画视图的 实际逻辑 方面的转换。

这些代表的工作方式就像您在两个 VC's 之间并与他们一起玩一样。

要成功完成转换,您必须执行以下步骤:

  1. 所以要使用它首先你需要

    • 设置modalPresentationStyle = .custom
    • 分配 transitonDelegate 属性.
  2. func animateTransition(_ : ) 你必须使用上下文 containerView 因为你在两个 VC's 之间所以你需要任何可以做任何动画的容器,所以上下文为您提供了可以制作动画的容器。

  3. 现在您需要 fromView & toViewVCA.view & VCB.view resp。现在在containerView中添加这两个视图,编写动画核心逻辑。

  4. 最后要注意的是在转换上下文对象上调用的 completeTransition(_:) 方法。动画完成后必须调用此方法,让系统知道您的视图控制器已完成转换。

这是过渡动画的核心基础。

我不知道 FB 动画,所以我只是解释了你剩下的问题。

参考

您可以询问任何进一步的信息。

Code Addition

关于图像选择

加入VC_A

var selectedImage: UIImageView?
 let transition = PopAnimator()

  override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)

        coordinator.animate(
          alongsideTransition: {context in
            self.bgImage.alpha = (size.width>size.height) ? 0.25 : 0.55
            self.positionListItems()
          },
          completion: nil
        )
      }
//position all images inside the list
  func positionListItems() {
    let listHeight = listView.frame.height
    let itemHeight: CGFloat = listHeight * 1.33
    let aspectRatio = UIScreen.main.bounds.height / UIScreen.main.bounds.width
    let itemWidth: CGFloat = itemHeight / aspectRatio

    let horizontalPadding: CGFloat = 10.0

    for i in herbs.indices {
      let imageView = listView.viewWithTag(i) as! UIImageView
      imageView.frame = CGRect(
        x: CGFloat(i) * itemWidth + CGFloat(i+1) * horizontalPadding, y: 0.0,
        width: itemWidth, height: itemHeight)
    }

    listView.contentSize = CGSize(
      width: CGFloat(herbs.count) * (itemWidth + horizontalPadding) + horizontalPadding,
      height:  0)
  }

// On image selection
VC_B.transitioningDelegate = self
    present(VC_B, animated: true, completion: nil)



   // add extension
extension VC_A: UIViewControllerTransitioningDelegate {

  func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    transition.originFrame = selectedImage!.superview!.convert(selectedImage!.frame, to: nil)

    transition.presenting = true
    selectedImage!.isHidden = true

    return transition
  }

  func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    transition.presenting = false
    return transition
  }
}

和动画class

class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {

  let duration = 1.0
  var presenting = true
  var originFrame = CGRect.zero

  var dismissCompletion: (()->Void)?

  func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return duration
  }

  func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    let containerView = transitionContext.containerView

    let toView = transitionContext.view(forKey: .to)!

    let herbView = presenting ? toView : transitionContext.view(forKey: .from)!

    let initialFrame = presenting ? originFrame : herbView.frame
    let finalFrame = presenting ? herbView.frame : originFrame

    let xScaleFactor = presenting ?

      initialFrame.width / finalFrame.width :
      finalFrame.width / initialFrame.width

    let yScaleFactor = presenting ?

      initialFrame.height / finalFrame.height :
      finalFrame.height / initialFrame.height

    let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor)

    if presenting {
      herbView.transform = scaleTransform
      herbView.center = CGPoint(
        x: initialFrame.midX,
        y: initialFrame.midY)
      herbView.clipsToBounds = true
    }

    containerView.addSubview(toView)
    containerView.bringSubview(toFront: herbView)

    UIView.animate(withDuration: duration, delay:0.0, usingSpringWithDamping: 0.4,
      initialSpringVelocity: 0.0,
      animations: {
        herbView.transform = self.presenting ?
          CGAffineTransform.identity : scaleTransform
        herbView.center = CGPoint(x: finalFrame.midX,
                                  y: finalFrame.midY)
      },
      completion:{_ in
        if !self.presenting {
          self.dismissCompletion?()
        }
        transitionContext.completeTransition(true)
      }
    )
  }

}

输出:

Git-集线器回购: https://github.com/thedahiyaboy/TDCustomTransitions

  • xcode : 9.2

  • swift : 4

UIViewControllerAnimatedTransitioning的核心方法是animateTransition。我在尝试解释基本思想时在这里添加了评论。

let duration = 0.5
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    // Works like an empty scratchpad/slate.
    // This is the view that will be shown on screen when animation starts and upto 
    // it ends.
    // Any animations done here are visible to user.
    // Nothing right now, in this container(*).
    let containerView = transitionContext.containerView

    // Grab the controller to animate from and to.
    let fromView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!.view
    let toView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!.view

    // We might need some other view's that need to be animated, like the UIImageView
    // In  your case this image view must exists on fromView and also in toView
    let fromImageView = UIImageView() // We should get image from fromController
    let toImageView = UIImageView() // We should get image from toController

    // Since the containerView has no views as of now(*), we need to add our fromView first
    containerView.addSubview(fromView!)

    // We will also add, the to view but with alpha 0 so that is not visible initially
    toView?.alpha = 0.0
    // Add this to view to container
    containerView.addSubview(toView!)


    UIView.animate(withDuration: duration, animations: { 
        // We do animations here, something like,
        fromImageView.frame = (toView?.frame)! // With some checking around the view relative frames
        toView?.alpha = 1.0
    }) { (completed) in
        // Do clean up here, after this completeTransition(true) method,
        // the comtainer will be removed from the screen and toView will be shown automatically
        transitionContext.completeTransition(true)
    }
}