The following gif demonstrates an animation issue that I am having. The small, red view is being animated. The small, green view marks the initial position. The red view is animated down and to the right, and is then auto-reversed. At the end, after returning to the initial position, the red view jumps to the left: it is this jump that I do not understand.
Here is the code. The animation has two parts: an initial change to the Y position and an X position change that kicks in half way through. Both animations are auto-reversed. Note the completion closure that updates the model to reflect the final view position which was brought about by the auto-reversal. It all seems correct to me. Why the jump to the left?
@objc private func animate() {
let center = animationView.center
let distance: CGFloat = 100
let down = {
UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.y += distance
}
let right = {
UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.x += distance
}
let animator = UIViewPropertyAnimator(duration: 2, curve: .linear, animations: down)
animator.addAnimations(right, delayFactor: 0.5)
animator.addCompletion { _ in self.animationView.center = center } // Sync up with result of the autoreverse
animator.startAnimation()
}
// ***********************************************************************
Update #1: The animation consists of two parts; down and right. In each of those two parts I am invoking UIView.setAnimationRepeatAutoreverse. If I comment out either or both of those auto reversals then everything works as expected (albeit I do not get the effect of the auto-reversal, but the behavior of the code is understandable).
// ***********************************************************************
Update #2: From Apple's documentation of UIView.setAnimationRepeatAutoreverse: Use of this method is discouraged in iOS 4.0 and later. Instead, you should use the animate(withDuration:delay:options:animations:completion:) method to specify your animations and the animation options.
Okay. Let's see what else is available to accomplish the delay of the second part of the animation.
At last!
private func animate() {
let distance: CGFloat = 100
let down = { self.animationView.center.y += distance }
let right = { self.animationView.center.x += distance }
let animator = UIViewPropertyAnimator(duration: 2, curve: .linear, animations: down)
animator.addAnimations(right, delayFactor: 0.5)
animator.pausesOnCompletion = true
let observer = animator.observe(\.isRunning, options: [.new] ) { animator, change in
print("isRunning changed to \(change.newValue!).")
if !animator.isRunning {
if !animator.isReversed {
print("Reversing animation")
animator.isReversed = true;
animator.startAnimation()
}
else {
print("Stopping animation")
animator.stopAnimation(false)
animator.finishAnimation(at: .start)
}
}
}
animator.addCompletion { position in
print("Animation completed at \(position). State = \(animator.state).")
observer.invalidate() // By capturing the observer here in the completion closure we keep it alive for the duration of the animation.
}
print("\nStarting animation")
animator.startAnimation()
print("Animation started")
}