【问题标题】:Why does UIView.animate work with an interactive controller transition, but UIViewPropertyAnimator doesn't?为什么 UIView.animate 可以与交互式控制器转换一起使用,但 UIViewPropertyAnimator 不能?
【发布时间】:2018-04-18 01:00:15
【问题描述】:

有关设置手势识别器等交互转换的样板,请参阅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()
    */
}

【问题讨论】:

    标签: ios swift uiview uinavigationcontroller uiviewpropertyanimator


    【解决方案1】:

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

    所以要得到你想要的:首先,你必须实现interruptibleAnimator(using:) 来创建动画师——你不能在animateTransition(using:) 中创建它。

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

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

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

    private var animatorForCurrentSession: UIViewImplicitlyAnimating?
    

    我的BackAnimator 实现中的属性 - 我将当前动画器存储在那里以在过渡尚未结束时返回它。

    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:) 返回的动画器不是我们启动的 - 环境会在适当的时候启动它。

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

    【讨论】:

    • 太棒了。我没有想到检索动画器并在可中断动画器中启动它。该解决方案在交互式和非交互式方面都表现出色。非常感谢。
    • 我最终使用了你的代码,但我在从 interruptibleAnimator(...) 返回之前启动了动画器。我将 animateTransition(...) 留空。我在某处读到这没问题,但现在找不到参考。我相信它在苹果的文档中。只是想分享。
    猜你喜欢
    • 2019-06-19
    • 1970-01-01
    • 2014-03-27
    • 1970-01-01
    • 2017-05-16
    • 1970-01-01
    • 1970-01-01
    • 2017-01-31
    • 2021-11-15
    相关资源
    最近更新 更多