ylliX - Online Advertising Network

UIViewPropertyAnimator is Too Cute


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:

  1. Before starting the animation
  2. Before setting alpha to 0.0
  3. After setting alpha to 0.0
  4. 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.)



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *