Chris
Chris

Reputation: 312

CABasicAnimation Doesn't End at toValue

I have a circular CAShapeLayer where I'd like to on tap, change the stroke and fill colors. To do so, I added a CABasicAnimation, but to my confusion, the animation doesn't end to the set toValue. Instead, the animation, resets back to the original color.

I tried using the animationDidStop delegate method in setting the stroke and fill colors to the desired end color, which worked, but a flashing glitch of the original colors appeared.

Any guidance would be appreciated.

func createCircleShapeLayer() {
    let layer = CAShapeLayer()     
    
    let circularPath = UIBezierPath(arcCenter: view.center, radius: 50, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
    layer.path = circularPath.cgPath
    layer.strokeColor = UIColor.red.cgColor
    layer.lineWidth = 10
    layer.fillColor = UIColor.clear.cgColor
    layer.lineCap = CAShapeLayerLineCap.round
    layer.frame = view.bounds
    
    view.layer.addSublayer(layer)
}

func animateLayerOnTap() {
    let strokeAnim = CABasicAnimation(keyPath: "strokeColor")
    
        strokeAnim.toValue           = UIColor.red.cgColor
        strokeAnim.duration          = 0.8
        strokeAnim.repeatCount       = 0
        strokeAnim.autoreverses      = false

    
    let fillAnim = CABasicAnimation(keyPath: "fillColor")
    
    fillAnim.toValue           = UIColor.lightGray.cgColor
    fillAnim.duration          = 0.8
    fillAnim.repeatCount       = 0
    fillAnim.autoreverses      = false

    layer.add(fillAnim, forKey: "fillColor")
    layer.add(strokeAnim, forKey: "strokeColor")
}

Upvotes: 0

Views: 253

Answers (1)

Duncan C
Duncan C

Reputation: 131471

UIView animation is pretty clean and simple to use. When you submit a block-based UIView animation, the system creates an animation that animates your view's properties to their end values, and they stay there. (Under the covers UIView animation adds one or more CAAnimations to the view's layer, and manages updating the view's properties to the final values once the animation is complete, but you don't need to care about those details.)

None of that is true with Core Animation. It's confusing, non-intuitive, and poorly documented. With Core animation, the animation adds a temporary "presentation layer" on top of your layer that creates the illusion that your properties are animating from their start to the their end state, but it is only an illusion.

By default, the presentation layer is removed once the animation is complete, and your layer seems to snap back to it's pre-animation state, as you've found. There are settings you can apply that cause the animation layer to stick around in it's final state once the animation is complete, but resist the urge to do that.

Instead, what you want to do is to set your layer's properties to their end state explicitly right after the animation starts. Making that more complicated, though, is the fact that a fair number of layer properties are "implicitly animated", meaning that if you change them, the system creates an animation that makes that change for you. (In that case, the change "sticks" once the implicit animation is complete.)

You are animating strokeColor and FillColor, which are both animatable. It would be easier to take advantage of the implicit animation of those layer properties. The tricky part there is that if you want to change any of the default values (like duration) for implicit animations, you have to enclose your changes to animatable properties between a call to CATransaction.begin() and CATransaction.commit(), and use calls to CATransaction to change those values.

Here is what your code to animate your layer's strokeColor and fillColor might look like using implicit animation and a CATransaction:

CATransaction.begin()
CATransaction.setAnimationDuration(0.8)
layer.strokeColor = UIColor.red.cgColor // Your strokeColor was already red?
layer.fillColor = UIColor.lightGray.cgColor
CATransaction.commit()

Upvotes: 2

Related Questions