I have a view that I want to animate the following way :
CGAffineTransform
Since I want to reuse the same easing function, I would like to have only one UIViewPropertyAnimator
and use its isReversed
property.
However when I try this, the result seems good on screen but the internal scale stays at 0.
Here's a simple ViewController that replicates the issue :
import UIKit
import Combine
class ViewController: UIViewController {
private var testView: UIView = {
let view = UIView()
view.backgroundColor = .red
return view
}()
private lazy var scaleAnimator = UIViewPropertyAnimator(duration: 2,
controlPoint1: CGPoint(x: 0.36, y: 0),
controlPoint2: CGPoint(x: 0.66, y: -0.56))
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(testView)
testView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
testView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
testView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
testView.widthAnchor.constraint(equalToConstant: 200),
testView.heightAnchor.constraint(equalToConstant: 200),
])
setupAnimation()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.startAnimation()
}
}
func setupAnimation() {
scaleAnimator.pausesOnCompletion = true
scaleAnimator.addAnimations {
self.testView.transform = CGAffineTransform(scaleX: 1, y: 1e-10) // Scale animations to exactly 0.0 don't work
}
scaleAnimator.publisher(for: \.isRunning, options: [.new])
.sink { [weak self] isRunning in
guard let self,
!isRunning else {
print("Animation start")
print("Scale Y : \(self!.testView.transform.d)")
return
}
guard !self.scaleAnimator.isReversed else {
print("Animation end")
print("Scale Y : \(self.testView.transform.d)")
return
}
print("Half animation")
print("Scale Y : \(self.testView.transform.d)")
self.testView.backgroundColor = .blue
self.scaleAnimator.isReversed = true
self.scaleAnimator.startAnimation()
}
.store(in: &cancellables)
}
func startAnimation() {
scaleAnimator.isReversed = false
scaleAnimator.startAnimation()
}
}
At the end, this code prints that the Y scale is 0, but on screen I have a 200px blue square. If I inspect the view, its scale is 0.
What am I doing wrong here ?
If we set:
scaleAnimator.pausesOnCompletion = true
when we get to the "Animation end" in this block:
guard !self.scaleAnimator.isReversed else {
print("Animation end")
print("Scale Y : \(self.testView.transform.d)")
return
}
the animator is still running.
If we change that to:
guard !self.scaleAnimator.isReversed else {
print("Animation end")
print("End 1 : Scale Y : \(self.testView.transform.d)")
self.scaleAnimator.stopAnimation(true)
print("End 2 : Scale Y : \(self.testView.transform.d)")
return
}
The "blue frame" and transform should be correct.
Quick testing, and I sometimes see transform.d == 0.9999999
, so you may want to do this:
self.scaleAnimator.stopAnimation(true)
self.testView.transform = .identity