简体   繁体   English

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

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

For boilerplate on setting up a gesture recognizer and such for the interactive transition, see this answer . 有关设置手势识别器的样板,以及交互式过渡,请参阅此答案

I am experimenting with interactive transitions, and spent quite a bit of time trying to figure out why the controllers would transition normally instead of scrubbing through according to the gesture. 我正在尝试交互式过渡,并花了相当多的时间试图弄清楚为什么控制器会正常转换而不是根据手势擦洗。 I discovered that it was not working because I am using a UIViewPropertyAnimator . 我发现它没有用,因为我使用的是UIViewPropertyAnimator Switching to the older UIView animation blocks work out of the box. 切换到较旧的UIView动画块开箱即用。 Why? 为什么? What is the difference in implementation? 实施有什么区别?

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()
    */
}

With introduction of the UIViewPropertyAnimator in iOS 10 the UIViewControllerAnimatedTransitioning protocol got updated, too. 随着iOS 10中UIViewPropertyAnimator引入, UIViewControllerAnimatedTransitioning协议也得到了更新。 They've added an optional func interruptibleAnimator(using: UIViewControllerContextTransitioning) that you don't have to implement (I guess for backward compatibility). 他们添加了一个可选的func interruptibleAnimator(using: UIViewControllerContextTransitioning) ,你不需要实现它(我想是为了向后兼容)。 But it was added exactly for the use case you mention here: to take advantage of the new UIViewPropertyAnimator . 但它完全针对您在此提及的用例添加:利用新的UIViewPropertyAnimator

So to get what you want: first, you have to implement interruptibleAnimator(using:) to create the animator - you don't create it in animateTransition(using:) . 所以要得到你想要的东西:首先,你必须实现interruptibleAnimator(using:)来创建动画师 - 你不要在animateTransition(using:)创建它animateTransition(using:)

As per comment in the source code of UIViewControllerAnimatedTransitioning (emphasis is mine)(I have no idea why the documentation does not contain this info): 根据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. 例如,它可以返回UIViewPropertyAnimator的实例。 It is expected that this method will return the same instance for the life of a transition. 预计此方法将在转换的生命周期内返回相同的实例。

You have to return the same animator for the duration of the transition. 您必须在转换期间返回相同的动画师。 That's why you will find 这就是你会发现的原因

private var animatorForCurrentSession: UIViewImplicitlyAnimating?

property in my BackAnimator implementation - I store the current animator there to return it if the transition haven't ended. 我的BackAnimator实现中的属性 - 如果转换尚未结束,我将当前的动画存储在那里以返回它。

When the interruptibleAnimator(using:) is implemented, the environment will take that animator and use it instead of animating using animateTransition(using:) . 当实现了interruptibleAnimator(using:) ,环境将使用该动画师并使用它而不是使用animateTransition(using:)进行动画animateTransition(using:) But to keep the contract of the protocol, animateTransition(using:) should be able to animate the transition - but you can simply use the interruptibleAnimator(using:) to create an animator and run the animation there. 但是为了保持协议的契约, animateTransition(using:)应该能够为转换设置动画 - 但是你可以简单地使用interruptibleAnimator(using:)创建一个动画师并在那里运行动画。

Following is a working BackAnimator implementation that you can use with the example you referred in this SO question . 以下是一个有效的BackAnimator实现,您可以使用此SO问题中提到的示例。 I used your code as basis, but you can simply swap my BackAnimator for their implementation and you are good to go (I was testing it on their example). 我使用你的代码作为基础,但你可以简单地交换我的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()
    }
}

Also notice that the animator returned by the interruptibleAnimator(using:) is not started by us - the environment will start it when appropriate. 另请注意, interruptibleAnimator(using:)返回的动画师不是由我们启动的 - 环境将在适当的时候启动它。

PS: Most of my knowledge on the subject comes from trying to implement an open source container that would allow custom interactive transitions between its containees - InteractiveTransitioningContainer . PS:我对这个主题的大部分知识来自于试图实现一个开源容器,它允许在其容器之间进行自定义交互式转换 - InteractiveTransitioningContainer Maybe you'll find there some inspiration, too :). 也许你会发现一些灵感:)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM