I was using the old UIView
APIs when I found some code that broke my brain. Here’s Swift code to animates a UIButton
from opaque to transparent over one second.
let button = ...
let animateOut = UIViewPropertyAnimator(duration: 1.0, curve: .easeInOut) {
print("2 alpha=\(button.alpha)")
button.alpha = 0.0
print("3 alpha=\(button.alpha)")
}
print("1 alpha=\(button.alpha)")
animateOut.startAnimation()
print("4 alpha=\(button.alpha)")
It prints the button’s alpha
four times:
- Before starting the animation
- Before setting
alpha
to0.0
- After setting
alpha
to0.0
- After starting the animation
Here’s the output:
1 alpha=1.0
2 alpha=1.0
3 alpha=0.0
4 alpha=0.0
And here’s it all running in the simulator:
The pixels don’t match the properties
The value of button.alpha
is 0.0 when startAnimation()
returns, and remains at 0.0 forever. But the actual drawn pixels are different! Over the one-second animation the pixels smoothly transition from 1.0 to 0.0.
I added code to print button.alpha
on every frame: it printed 0.0 each time.
It’s bananas that the alpha
property doesn’t match the drawn pixels!
State capture
I’m more familiar with Android’s animation code. Instead of directly assigning properties to their target values as above (button.alpha = 0.0
), Android requires me to use Property
objects, like View.ALPHA
:
val animateOut = ObjectAnimator.ofFloat(button, View.ALPHA, 0f)
For each property I want to animate, I need to find its Property
object or create one. Android uses it to read the initial value and to write an update for each frame.
But hang on a sec… how does iOS do capture the initial value? Somehow UIViewPropertyAnimator
figures out the initial value of each animated property. It also keeps track of those values somewhere. I don’t know how this works.
The pixels don’t match the properties, again
This whole API is such a curiosity! What happens if I change the property while it’s being animated?
...
animateOut.startAnimation()
print("4 alpha=\(button.alpha)")
button.alpha = 1.0
print("5 alpha=\(button.alpha)")
So when the animation completes, the value I set takes effect. I suspect the animation overrides the actual property while it’s running.
Only certain properties
What happens if I animate the tint color too?
...
let animateOut = UIViewPropertyAnimator(duration: 1.0, curve: .easeInOut) {
print("2 alpha=\(button.alpha)")
button.alpha = 0.0
button.tintColor = .red
print("3 alpha=\(button.alpha)")
}
...
The color changes instantly, and the alpha animates over a second. Yuck.
Too Cute
The UIViewPropertyAnimator
API can do amazing and powerful things! But it’s built on mechanisms that I can’t use. And because there’s no source code to browse, I can’t study it to learn how it works.
UPDATE, AN HOUR LATER
Khaos Tian, who knows iOS better than I do, read this post and referred me to CALayer.presentation(). That API holds properties during an animation. Super cool!
(I’m still trying to figure out how the accessing alpha
inside a UIViewPropertyAnimator
block creates a property animation.)