为什么 UIView.animate 可以使用交互式控制器转换,而 UIViewPropertyAnimator 不能?

Why does UIView.animate work with an interactive controller transition, but UIViewPropertyAnimator doesn't?

有关设置手势识别器等交互式过渡的样板文件,请参阅 this answer

我正在试验交互式过渡,并花了很多时间试图弄清楚为什么控制器会正常过渡而不是根据手势擦洗。我发现它不起作用,因为我使用的是 UIViewPropertyAnimator。切换到旧的 UIView 动画块开箱即用。为什么?实现上有什么区别?

func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
{
    // Ignore the forced unwrapping, for sake of brevity.
    let view_From       = transitionContext.viewController(forKey: .from)!.view!
    let view_To         = transitionContext.viewController(forKey: .to)!.view!
    transitionContext.containerView.insertSubview(view_To, aboveSubview: view_From)

    view_To.alpha = 0

    // This animation block works - it will follow the progress value of the interaction controller
    UIView.animate(withDuration: 1, animations: {
        view_From.alpha = 0.0
        view_To.alpha = 1.0
    }, completion: { finished in
        transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
    })

    // This animation block fails - it will play out normally and not be interactive
    /*
    let animator = UIViewPropertyAnimator(duration: 1, curve: .linear)
    animator.addAnimations {
        view_To.alpha = 1
        view_From.alpha = 0
    }
    animator.addCompletion { (position) in
        switch position {
        case .end: print("Completion handler called at end of animation")
        case .current: print("Completion handler called mid-way through animation")
        case .start: print("Completion handler called  at start of animation")
        }
        transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
    }
    animator.startAnimation()
    */
}

随着 UIViewPropertyAnimator 在 iOS 10 中的引入,UIViewControllerAnimatedTransitioning 协议也得到了更新。他们添加了一个您不必实现的可选 func interruptibleAnimator(using: UIViewControllerContextTransitioning)(我想是为了向后兼容)。但它是为您在此处提到的用例添加的:利用新的 UIViewPropertyAnimator.

所以要得到你想要的东西:首先,你必须实现 interruptibleAnimator(using:) 来创建动画器 - 你不会在 animateTransition(using:).

中创建它

根据 UIViewControllerAnimatedTransitioning 源代码中的注释(重点是我的)(我不知道为什么文档不包含此信息):

A conforming object implements this method if the transition it creates can be interrupted. For example, it could return an instance of a UIViewPropertyAnimator. It is expected that this method will return the same instance for the life of a transition.

在过渡期间,您必须 return 同一位动画师。这就是为什么你会发现

private var animatorForCurrentSession: UIViewImplicitlyAnimating?

属性 在我的 BackAnimator 实现中 - 如果过渡还没有结束,我将当前的动画师存储到 return 它。

interruptibleAnimator(using:) 实现时,环境将采用该动画师并使用它而不是使用 animateTransition(using:) 进行动画处理。 但要保持合同协议,animateTransition(using:) 应该能够为过渡设置动画 - 但您可以简单地使用 interruptibleAnimator(using:) 创建一个动画师,然后 运行 那里的动画。

以下是一个有效的 BackAnimator 实现,您可以将其与您在 this SO question 中提到的示例一起使用。我使用你的代码作为基础,但你可以简单地将我的 BackAnimator 换成他们的实现,你就可以开始了(我在他们的例子中测试它)。

class BackAnimator : NSObject, UIViewControllerAnimatedTransitioning {
    // property for keeping the animator for current ongoing transition
    private var animatorForCurrentTransition: UIViewImplicitlyAnimating?

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

    func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
        // as per documentation, the same object should be returned for the ongoing transition
        if let animatorForCurrentSession = animatorForCurrentTransition {
            return animatorForCurrentSession
        }
        // normal creation of the propertyAnimator
        let view_From       = transitionContext.viewController(forKey: .from)!.view!
        let view_To         = transitionContext.viewController(forKey: .to)!.view!
        transitionContext.containerView.insertSubview(view_To, aboveSubview: view_From)

        view_To.alpha = 0
        let animator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), curve: .linear)
        animator.addAnimations {
            view_To.alpha = 1
            view_From.alpha = 0
        }
        animator.addCompletion { (position) in
            switch position {
            case .end: print("Completion handler called at end of animation")
            case .current: print("Completion handler called mid-way through animation")
            case .start: print("Completion handler called  at start of animation")
            }
            // transition completed, reset the current animator:
            self.animatorForCurrentTransition = nil

            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
        // keep the reference to current animator
        self.animatorForCurrentTransition = animator
        return animator
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        // animateTransition should work too, so let's just use the interruptibleAnimator implementation to achieve it
        let anim = self.interruptibleAnimator(using: transitionContext)
        anim.startAnimation()
    }
}

另请注意,interruptibleAnimator(using:) 编辑的动画器 return 不是由我们启动的 - 环境将在适当的时候启动它。

P.S.: 我关于这个主题的大部分知识都来自于尝试实现一个开源容器,该容器允许在其容器之间进行自定义交互转换 - InteractiveTransitioningContainer。也许你也会从中找到灵感 :).