Question

I have a CAShapeLayer in form of a square and I set the width to be 2.0f.

I would like to animate its strokeEnd and for that I use a CABasicAnimation like so:

CABasicAnimation *stroke = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
stroke.fromValue = @(0);
stroke.toValue = @(1); 
stroke.repeatCount = 1;
stroke.duration = 10.0f;
stroke.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[myLayer addAnimation:stroke forKey:nil];

And it works. From the book by Matt Neuberg, I understand that we could omit the toValue and fromValue altogether by:

[CATransaction begin];
[CATransaction setDisableActions:YES];
..
..
[CATransaction commit];

His example was, however, based on transform. And I am trying to achieve the same instead with strokeEnd.

The best I can do is to set the strokeEnd within CATransaction block to either 1 or 0 and set the fromValue correspondingly. Other words I don't seem to be able omit both toValue and endValue on animating strokeEnd - I need at least one of them (i.e., fromValue or toValue).

So my question is, how could I be able to animate strokeEnd without needing to use toValue and fromValue at all?

Thanks.

Was it helpful?

Solution

There are two very different issues at play here:

  1. If you change one of your layer's animatable properties (i.e. not with a CABasicAnimation, but rather changing the layer's properties in code, directly) Core Animation may apply implicit animations to the layer (a relatively fast animation, but an animation nonetheless). For example, if you change myLayer.opacity, Core Animation may automatically apply implicit animations to fade this effect. Likewise if you directly change myLayer.strokeEnd, it may animate the change of the stroke end (you'll see it quickly drawing the line, rather than just immediately appearing).

    By using [CATransaction setDisableActions:YES], you can instruct Core Animation to not perform these implicit animations that may apply when you change the layer's properties directly. By using [CATransaction setDisableActions:NO], you will re-enable the implicit animations.

  2. When you create a CABasicAnimation, there are fromValue, toValue, and byValue properties (which according to the documentation, "All are optional, and no more than two should be non-nil."). Frequently you'll see animations that specify both the fromValue and toValue. But you want, you can specify one, and Core Animation will determine what the others should be.

    For example, let's assume you just created a CAShapeLayer (and by default, strokeEnd is 1.0). Then if I want to animate strokeEnd from 0 to 100%, you can just use:

    CABasicAnimation *stroke = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    stroke.fromValue = @(0.0);
    stroke.duration = 1.0f;
    [myLayer addAnimation:stroke forKey:nil];
    

    Note, I did not have to specify the toValue of @(1.0), because it will automatically default to the current value of myLayer.strokeEnd (which happens to be 1.0 in this example).

Where these two disparate aspects of Core Animation interact is when you're writing an animation using CABasicAnimation in which you not only want to specify the animation, but you also want to alter the properties of the layer, directly, right before the animation commences.

For example, if I added a CAShapeLayer as a sublayer, but wanted to animate the drawing of the first half of it (i.e. animate strokeEnd from 0.0 to 0.5), you might theoretically want to (a) specify myLayer.shapeEnd to be the final value (e.g. 0.5); and (b) create a CABasicAnimation with a fromValue of @(0.0) (but you could omit toValue because we've already set the layer's shapeEnd to the appropriate value). But, going back to point 1 at the start of my answer, when we set a layer property directly, such as myLayer.shapeEnd, you might want to setDisableActions to YES, so there's no attempt at any implicit animation (given that we're about to specify the actual desired animation with the CABasicAnimation).

// set the layer's strokeEnd directly (with no implicit animation)

[CATransaction setDisableActions:YES];
myLayer.strokeEnd = 0.5;
[CATransaction setDisableActions:NO];

// now animate the layer's stroke end from 0.0 to the final value,
// which, because we didn't specify `toValue`, will default to the
// current value of `myLayer.strokeEnd` (which we happened to set
// right above)

CABasicAnimation *stroke = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
stroke.fromValue = @0.0;
stroke.duration = 1.0f;
[myLayer addAnimation:stroke forKey:nil];

In practice, I find the setDisableActions is often unnecessary, but technically, if you're setting the layer's properties and you want to ensure that there's no implicit animation initiated, you may wish to use setDisableActions, as shown above.

But, as you might infer from this discussion, the fact that we do not need to set toValue is not a direct result of using CATransaction or its method setDisableActions. The toValue is not needed in this example because we set the layer's strokeEnd before starting the animation. We just happen to use CATransaction setDisableActions to ensure there's no implicit animation initiated when changing layer properties directly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top