Reputation: 3827
I have a CALayer with a custom animation on it, working via an @NSManaged
property, and overriding:
class func defaultValue(forKey key: String) -> Any?
class func needsDisplay(forKey key: String) -> Bool
func action(forKey key: String) -> CAAction?
func display()
However, I sometimes want to bypass the animation and have the property immediately step to the new value. In my CALayer
sub-class I tried this:
@NSManaged private var radius: CGFloat
func animate(to radius: CGFloat) {
self.radius = radius
}
func step(to radius: CGFloat) {
// Inspired by https://stackoverflow.com/a/34941743
CATransaction.begin()
CATransaction.setDisableActions(true) // Prevents animation occurring
self.radius = radius
CATransaction.commit()
setNeedsDisplay() // Has no effect
}
When animate(to:)
is called, display()
is called repeatedly and my custom drawing code can do it's thing. When step(to:)
is called, the CATransaction
code does prevent an animation from occurring, but no drawing is ever performed at all.
I can get it to behave as desired, but it feels quite hacky:
func step(to radius: CGFloat) {
// func action(forKey key: String) -> CAAction? uses .animationDuration
// when constructing a CABasicAnimation
let duration = animationDuration
defer { animationDuration = duration }
animationDuration = 0
self.radius = radius
}
What is the correct method to give the caller the ability to choose whether the property animates from one value to the next, or steps immediately? A subsequent change to radius
should respect the previous value, whether it was stepped or animated to.
Upvotes: 0
Views: 222
Reputation: 536027
You say you have implemented action(forKey:)
(quite rightly). So on those occasions when you don't want this property to be animated, return nil
from that method. The drawing will still take place, but without animation.
Alternatively, you could return super.action(forKey:key)
. That might be a little more sane, but the outcome is the same.
You may ask (and I hope you do): How can I throw some kind of switch that action(forKey:)
can consult in order to know which kind of occasion this is? One possibility is to set a property of the layer using key-value coding.
CALayer has a wonderful feature that you are allowed to call setValue(_:forKey:)
or value(forKey:)
for any key; it doesn't have to be a "real" key that already exists.
So you could call setValue(false, forKey:"shouldAnimate")
on the layer before setting the property. And your action(forKey:)
can then consult value(forKey:"shouldAnimate")
, and see whether it is false
(as opposed to true
or nil
) — and if it is, it returns nil
to prevent the animation.
Upvotes: 1