Implicit animation of strokeStart plays before delayed explicit

This is the animation I want to achieve: a tick draws in, wait 1 sec and then draw out.

  1. strokeStart = 0.0 strokeEnd = 0.0
  2. strokeStart = 0.0 strokeEnd = 1.0
  3. strokeStart = 1.0 strokeEnd = 1.0

The first animation is working good. Then in the delegate callback I set up the new animation with 1 sec delay in beginTime.

But what actually happens is that an implicit animation plays first I guess becuase of setting tickLayer's strokeStart to 1.0. And after 1 sec the other animation plays as well.


I have no idea why is this happening. I set the key correctly when adding the animation.


     func animateTick() {
        ...
        tickLayer.strokeStart = 0.0
        tickLayer.strokeEnd = 1.0
  
        layer.addSublayer(tickLayer)
  
        let inAnimation = CABasicAnimation(keyPath: "strokeEnd")
        inAnimation.fromValue = 0.0
        inAnimation.toValue = 1.0
        inAnimation.duration = 0.3
        inAnimation.delegate = self
  
        tickLayer.addAnimation(inAnimation, forKey: "strokeEnd")
    }

    override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
  
        let now = tickLayer.convertTime(CACurrentMediaTime(), fromLayer: nil)
  
        tickLayer.strokeStart = 1.0
  
        let outAnimation = CABasicAnimation(keyPath: "strokeStart")
        outAnimation.fromValue = 0.0
        outAnimation.toValue = 1.0
        outAnimation.duration = 0.5
        outAnimation.beginTime = now + 1.0
  
        tickLayer.addAnimation(outAnimation, forKey: "strokeStart")
    }
Accepted Answer

It appears to me that what's going on is that, quite simply, the default implicit animation fires before you explicit animation engages to "cover up" what's really happening in the model. Remember that explicit animations do not change the model values—that is, if you were to access strokeStart or strokeEnd during the animations, you would not get the actual value the user sees on screen.

The reason your first animation works is that you create the explicit animation and set it up to run immediately. You never get a chance to see the implicit animation doing its thing. In the second animation, however, you're pushing the explicit animation back one second, which allows the implicit animation to have the layer to itself until the explicit animation kicks in.

To solve your problem, I would rewrite animationDidStop() like this, with the changes I've made in bold:

override func animationDidStop(anim: CAAnimation, finished flag: Bool) {

if anim.keyPath == "strokeEnd" {

let now = tickLayer.convertTime(CACurrentMediaTime(), fromLayer: nil)

tickLayer.strokeStart = 1.0

let outAnimation = CABasicAnimation(keyPath: "strokeStart")

outAnimation.fromValue = 0.0

outAnimation.toValue = 1.0

outAnimation.duration = 0.5

outAnimation.beginTime = now + 1.0

outAnimation.delegate = self


tickLayer.addAnimation(outAnimation, forKey: "strokeStart")

} else {

CATransaction.begin()

CATransaction.disableActions = true

tickLayer.strokeStart = 1.0

CATransaction.commit()

}

}

Now, no implicit animation will occur because the model is not being updated during the animation. Once your animation is finished, animationDidStop() will first set up a special CATransaction with implicit animation disabled and update the model.

I haven't worked with Core Animation in a while, so hopefully I'm not steering you wrong. Let me know if you need more help.

Oh, one more thing. If you need to see what the animations are doing at any time, just call tickLayer.presentationLayer.strokeStart or tickLayer.presentationLayer.strokeEnd. Check out the Core Animation Programming Guide for more info on implicit/explicit animations and the presentation layer.

Thanks for the solution. The animation is working correctly now.

I have two follow-up questions if you have time to answer them:

  1. How does CATransaction know which layer to apply the transactions to. For example here you set the disabled actions but do not specify the layer.
  2. You said that you haven't worked with CA for a while. Is this because there is a better alternative, or you just did not needed Animations? I am asking this because I want to put some cool animations in one of my app, and if I learn a way to do it, it should be the most up to date and powerful.

To answer both of your questions:


1. CATransaction is fairly global, almost exactly like NSAnimationContext if you're familiar with that. The transaction applies to anything and everything that is triggered inside of its grouping (denoted by the begin() and commit() functions).


2. I stopped using Core Animation because I haven't needed its unique behavior for a while. It's very specialized software, so during the development of my app (which is very graphics-intensive), I realized that the CA-based core wasn't capable of delivering what I needed. So there might be a better alternative depending on what you're trying to create, but Core Animation is still useful and valuable. I think you've Besides, its rendering system has been upgraded to use Metal, so it should continue to stay relevant for quite a while yet.

Implicit animation of strokeStart plays before delayed explicit
 
 
Q