Search code examples
iphonecore-animationcaanimationcakeyframeanimationcatransaction

How do I successfully animate multiple CALayers simultaneously?


I'm successfully animating a single layer to alter its position along an arbitrary path on my screen. I'm now attempting to replicate this animation multiple times to give the illusion of something bending round a corner. I put the code inside a CATransaction and put it in a loop incrementing the starting position for each iteration of the loop, then committed the CATransaction after the loop. The effect I see is the same if the code was not in a loop (That is, just one layer being animated) then at the end of the animation all the layers appear (Before my delegate removes them at the end of the animation in animationDidStop)

The code I have written looks like:

NSArray* path = [board caclulatePath:s];
    [CATransaction begin];
    [CATransaction setValue:[NSNumber numberWithFloat:([path count] * 0.25)] forKey:kCATransactionAnimationDuration];
    for (int i = 0; i < 20; i++)
    {
        CALayer* laserLayer = [CALayer layer];
        laserLayer.bounds = CGRectMake(s.frame.origin.x, s.frame.origin.y + (10*i), 20, 10);
        laserLayer.position = CGPointMake(s.frame.origin.x + (s.frame.size.width / 2), s.frame.origin.y + (s.frame.size.height / 2) + (10*i));
        laserLayer.contents = (id)[UIImage imageNamed:@"Laser.png"].CGImage;
        [self.layer addSublayer:laserLayer];

        CAKeyframeAnimation* anim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        anim.values = path;
        anim.duration = ([path count] * 0.25);
        anim.removedOnCompletion = NO;
        anim.delegate = self;
        anim.rotationMode = kCAAnimationRotateAuto;
        [anim setValue:@"Fire" forKey:@"Action"];
        [anim setValue:laserLayer forKey:@"Layer"];
        [laserLayer addAnimation:anim forKey:nil];
    }
    [CATransaction commit];

where [board caclulatePath:s] returns an NSArray* of NSValues representing CGPoints.

How can I achieve the effect I'm after (Which is multiple copies of laser.png following the same path)? [Laser.png is a 20px x 20px red square];


Solution

  • The actual problem was that each layer WAS following the same path, at the same time... The solution was to fire off each Layer / Animation after a delay of (someSmallFractionOfTime * i) where i was incremented.

    So I extracted the animation part as a new function / method / message (Whatever it's called)

    - (void) kickoffLaserAnimationWithPath: (NSArray *) path  {
    CGPoint start = [(NSValue*)[path objectAtIndex:0] CGPointValue];
    CALayer* laserLayer = [CALayer layer];
    laserLayer.bounds = CGRectMake(start.x, start.y, 20, 10);
    laserLayer.position = CGPointMake(start.x, start.y);
    laserLayer.contents = (id)[UIImage imageNamed:@"Laser.png"].CGImage;
    [self.layer addSublayer:laserLayer];
    
    CAKeyframeAnimation* anim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    anim.values = path;
    anim.duration = ([path count] * laserSpeed);
    anim.removedOnCompletion = NO;
    anim.delegate = self;
    anim.rotationMode = kCAAnimationRotateAuto;
    [anim setValue:@"Fire" forKey:@"Action"];
    [anim setValue:laserLayer forKey:@"Layer"];
    [laserLayer addAnimation:anim forKey:nil];
    isAnimating = YES;
    
    }
    

    and called it within the loop like so:

    NSArray* path = [board caclulatePath:s];
        [CATransaction begin];
        [CATransaction setValue:[NSNumber numberWithFloat:([path count] * laserSpeed)] forKey:kCATransactionAnimationDuration];
        float numBetweenPoints = (float)((float)s.frame.size.height / (float)10) * 2;
        float delay = (laserSpeed / numBetweenPoints);
        for (int i = 0; i < [path count]; i++)
        {
            [self performSelector:@selector(kickoffLaserAnimationWithPath:) withObject:path afterDelay:(delay*i)];
    
        }
        [CATransaction commit];
    

    Voila...