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
. Switching to the older UIView animation blocks work out of the box. 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 theUIViewControllerAnimatedTransitioning
protocol got updated, too. They've added an optionalfunc interruptibleAnimator(using: UIViewControllerContextTransitioning)
that you don't have to implement (I guess for backward compatibility). But it was added exactly for the use case you mention here: to take advantage of the newUIViewPropertyAnimator
.So to get what you want: first, you have to implement
interruptibleAnimator(using:)
to create the animator - you don't create it inanimateTransition(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):You have to return the same animator for the duration of the transition. That's why you will find
property in my
BackAnimator
implementation - I store the current animator there to return it if the transition haven't ended.When the
interruptibleAnimator(using:)
is implemented, the environment will take that animator and use it instead of animating usinganimateTransition(using:)
. But to keep the contract of the protocol,animateTransition(using:)
should be able to animate the transition - but you can simply use theinterruptibleAnimator(using:)
to create an animator and run the animation there.Following is a working
BackAnimator
implementation that you can use with the example you referred in this SO question. I used your code as basis, but you can simply swap myBackAnimator
for their implementation and you are good to go (I was testing it on their example).Also notice that the animator returned by the
interruptibleAnimator(using:)
is not started by us - the environment will start it when appropriate.P.S.: 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. Maybe you'll find there some inspiration, too :).