为什么UIView.animate使用交互式控制器转换,但UIViewPropertyAnimator不支持?

时间:2017-11-05 22:15:21

标签: ios swift uiview uinavigationcontroller uiviewpropertyanimator

有关设置手势识别器以及交互式过渡的样板文件,请参阅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()
    */
}

1 个答案:

答案 0 :(得分:12)

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

为了得到你想要的东西:首先,你必须实施interruptibleAnimator(using:)来创建动画师 - 你不能在animateTransition(using:)中创建它。

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

  

如果可以中断其创建的转换,则符合对象会实现此方法。例如,它可以返回UIViewPropertyAnimator的实例。 预计此方法将在转换生命周期内返回相同的实例。

你必须在过渡期间返回相同的动画师。这就是为什么你会找到

的原因
private var animatorForCurrentSession: UIViewImplicitlyAnimating?
我的BackAnimator实现中的

属性 - 如果过渡天堂已经结束,我会将当前的动画存储在那里以返回它。

当实施interruptibleAnimator(using:)时,环境将使用该动画制作者并使用它而不是使用animateTransition(using:)制作动画。但是为了保持协议的合同,{{ 1}}应该能够为过渡设置动画 - 但您只需使用animateTransition(using:)创建动画师并在那里运行动画。

以下是一个有效的interruptibleAnimator(using:)实施,您可以将其与this SO question中提到的示例一起使用。我使用你的代码作为基础,但你可以简单地交换我的BackAnimator来实现它们,你很高兴(我在他们的例子中测试它)。

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() } } 返回的动画师未由我们启动 - 环境将在适当的时候启动。

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