0
votes

CAAnimation does not provide a mechanism for assigning callback functions other than the standard "animationDidStart:"/"animationDidStop:" methods.

I have a custom UIControl that utilizes 2 CALayers that overlap. The purpose of this control is similar to an old fashioned sonar. The top layer's contents contains an image that gets rotated constantly (call this layer "wand"). Beneath that layer is a "spriteControl" layer that renders blips as the wand passes over them.

The objects that the blips represent are pre-fetched and organized into invisible CAShapeLayers by the spriteControl. I am using a CABasicAnimation to rotate the wand 10 degrees at a time, then utilizing the "animationDidStop:" method to invoke a method on the spriteControl that takes the current rotation value of the wand layer (a.k.a. heading) and animates the alpha setting from 1.0 to 0.0 for simulating the blip in and fade out effect. Finally, the process is started over again indefinitely.

While this approach of using the CAAnimation callbacks ensures that the timing of the wand reaching a "ping" position (i.e. 10deg, 20deg, 270deg, etc) always coincide with the lighting of the blips in the other layer, there is this issue of stopping, recalculating, and starting the animation every 10 degrees.

I could spawn an NSTimer to fire a method that queries the angle of the wand's presentation layer to get the heading value. However, this makes it more difficult to keep the wand and the blip highlighting in sync, and/or cause some to get skipped altogether. This approach is discussed a bit here: How can I callback as a CABasicAnimation is animating?

So my question is whether or not there is anything I can do to improve the performance of the wand layer rotation without reimplementing the control using OpenGL ES. (I realize that this would be easily solved in an OpenGL environment, however, to use it here would require extensive redesign that simply isn't worth it.) While the performance issue is minor, I can't shake the feeling that there is something simple and obvious that I could do that would allow the wand to animate indefinitely without pausing to perform expensive rotation calculations in between.

Here is some code:

- (void)rotateWandByIncrement
{
    if (wandShouldStop)
        return;

    CGFloat newRotationDegree = (wandRotationDegree + WAND_INCREMENT_DEGREES);
    if (newRotationDegree >= 360)
        newRotationDegree = 0;


    CATransform3D rotationTransform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(newRotationDegree), 0, 0, 1);

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
    animation.toValue = [NSValue valueWithCATransform3D:rotationTransform];
    animation.duration = WAND_INCREMENT_DURATION;
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = FALSE;
    animation.delegate = self;

    [wandLayer addAnimation:animation forKey:@"transform"];
}

- (void)animationDidStart:(CAAnimation *)theAnimation
{
    if (wandShouldStop)
        return;

    NSInteger prevWandRotationDegree = wandRotationDegree - WAND_INCREMENT_DEGREES;
    if (prevWandRotationDegree < 0)
        prevWandRotationDegree += 360;

    // Pulse the spriteControl
    [[self spriteControl] pulseRayAtHeading:prevWandRotationDegree];
}

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
    // update the rotation var
    wandRotationDegree += WAND_INCREMENT_DEGREES;
    if (wandRotationDegree >= 360)
        wandRotationDegree = 0;

    // This applies the rotation value to the model layer so that
    // subsequent animations start where the previous one left off
    CATransform3D rotationTransform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(wandRotationDegree), 0, 0, 1);

    [CATransaction begin];
    [CATransaction setDisableActions:TRUE];
    [wandLayer setTransform:rotationTransform];
    [CATransaction commit];

    //[wandLayer removeAnimationForKey:@"transform"];
    [self rotateWandByIncrement];
}
1
Update: I have since eliminated all unnecessary calculations by creating a static array of precomputed values. I have now assured myself that my performance bottleneck is not caused by expensive math operations. I then implemented keyframe animations on the wand layer, but still get a slightly less-than-smooth rotation. I actually found that switching to UIView animation rather than CAAnimation provided smoother animation in this circumstance. So far, seems like to get any real performance benefit from CAAnimation, you want to avoid using callbacks altogether and just let the GPU do its thing.quickthyme

1 Answers

1
votes

Let's say it takes 10 seconds for the radar to make one complete rotation.

To get the wand to rotate indefinitely, attach a CABasicAnimation to it with its Duration property set to 10 and its RepeatCount property set to 1e100f.

The blips can each be animated using their own instance CAKeyframeAnimation. I won't write the details, but for each blip, you specify an array of opacity values (I assume opacity is how you're fading out the blips) and an array of time percentages (see Apple's documentation).