0

I'm trying to chain two keyframe-based animations, but the second animation won't play for some reason. Any idea what's going on?

// Create an animation group to contain all album art animations
    CAAnimationGroup *albumArtAnimationGroup = [CAAnimationGroup animation];
    albumArtAnimationGroup.duration = 3.0;
    albumArtAnimationGroup.repeatCount = 0;

    // First album art translation animation
    CGMutablePathRef albumCoverPath = CGPathCreateMutable();
    CGPathMoveToPoint(albumCoverPath, NULL,
                      albumCover.layer.position.x,
                      albumCover.layer.position.y);
    CGPathAddLineToPoint(albumCoverPath, NULL,
                         albumCover.layer.position.x-[UIScreen mainScreen].bounds.size.height,
                         albumCover.layer.position.y);
    CAKeyframeAnimation *albumCoverTranslationAnimation;
    albumCoverTranslationAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    albumCoverTranslationAnimation.calculationMode = kCAAnimationLinear;
    albumCoverTranslationAnimation.path = albumCoverPath;
    albumCoverTranslationAnimation.duration = 1.0;

    // Second album art translation animation
    CGMutablePathRef albumCoverPath1 = CGPathCreateMutable();
    CGPathMoveToPoint(albumCoverPath1, NULL,
                      albumCover.layer.position.x+[UIScreen mainScreen].bounds.size.height,
                      albumCover.layer.position.y);
    CGPathAddLineToPoint(albumCoverPath1, NULL,
                         albumCover.layer.position.x,
                         albumCover.layer.position.y);
    CAKeyframeAnimation *albumCoverTranslationAnimation1;
    albumCoverTranslationAnimation1 = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    albumCoverTranslationAnimation1.calculationMode = kCAAnimationLinear;
    albumCoverTranslationAnimation1.path = albumCoverPath1;
    albumCoverTranslationAnimation1.duration = 1.0;
    CFTimeInterval localAlbumLayerTime = [albumCover.layer convertTime:CACurrentMediaTime() fromLayer:nil];
    albumCoverTranslationAnimation1.beginTime = localAlbumLayerTime + 1.0;

 albumArtAnimationGroup.animations = @[albumCoverTranslationAnimation, albumCoverTranslationAnimation1];

    [albumCover.layer addAnimation:albumArtAnimationGroup forKey:@"position"];

Edit

Solved. It turns out that either Apple's documentation was misleading, or I was using CACurrentMediaTime incorrectly. The code below did the trick.

albumArtAnimationGroup.duration = 2.0;
albumCoverTranslationAnimation.duration = 1.0;
albumCoverTranslationAnimation1.beginTime = 1;
albumArtAnimationGroup.animations = @[albumCoverTranslationAnimation, albumCoverTranslationAnimation1];
[albumCover.layer addAnimation:albumArtAnimationGroup forKey:@"position"];

However, according to Apple, I may possibly run into issues regarding the timing since I am not using CACurrentMediaTime(), as shown below.

To assist you in making sure time values are appropriate for a given layer, the CALayer class defines the convertTime:fromLayer: and convertTime:toLayer: methods. You can use these methods to convert a fixed time value to the local time of a layer or to convert time values from one layer to another. The methods take into account the media timing properties that might affect the local time of the layer and return a value that you can use with the other layer. Listing 5-3 shows an example that you should use regularly to get the current local time for a layer. The CACurrentMediaTime function is a convenience function that returns the computer’s current clock time, which the method takes and converts to the layer’s local time.

Listing 5-3 Getting a layer’s current local time

CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];
ratsimihah
  • 1,010
  • 1
  • 11
  • 22
  • I was previously using animateKeyFramesWithDuration, but I need more flexibility. And keyframe animations are still in the Core Animation programming guide. Any insight? – ratsimihah Mar 19 '14 at 23:49
  • Is it possible to chain animations using animateWithDuration and repeat that sequence? – ratsimihah Mar 19 '14 at 23:52
  • @Fogmeister, nonsense. CAKeyframeAnimation is still fully supported. It's harder to use than UIView animation, but it's the only way to animate an object along an explicit CGPath. There are lots of complex animations that still require `CAKeyframeAnimation`s, `CAAnimationGroup`s, `CABasicAnimation`s, etc. – Duncan C Mar 20 '14 at 00:09
  • @DuncanC sorry, you're right. I was thinking of the wrong animation type. – Fogmeister Mar 20 '14 at 00:13
  • @ratsimihah, the difference is the animation group. For animations that are in an animation group the beginTime property takes on a different meaning. Instead of specifying a beginTime based on the currentMediaTime or the time for the current layer, it is based on the duration of the animation group. – Duncan C Mar 20 '14 at 17:48
  • (the documentation on this stuff is almost nonexistent. You might want to buy one or more books on Core Animation. I just ordered Nick Lockwood's new book. The other 2 books on the subject are good, but rather outdated now.) – Duncan C Mar 20 '14 at 18:01

1 Answers1

1

When you add multiple animations to an animation group, the beginTime property starts at 0, and ends at the duration of the animation. So to chain a second animation, set it's beginTime to the duration of the first animation, and make the animation group's duration long enough for the entire animation sequence.

BTW, it might be simpler to create a single CAKeyframeAnimation that has the 2 paths combined together into one. It would be a lot less code.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • Thank you. I need a pause between the two animations. Would that be achievable with a single two-paths keyframe animation? – ratsimihah Mar 20 '14 at 15:34
  • I found a way to fix it, and it's in agreement with your suggestion. If you look at my edit, I may still run into issues with timing, though. – ratsimihah Mar 20 '14 at 15:51
  • @ratsimihah, No, I don't think you could create a pause between animations if they both had the same path - unless you add lots and lots of calls to lineToPoint to the same point, and don't used paced timing. But that would be a hack. Better to use an animation group as you are doing in that case. – Duncan C Mar 20 '14 at 16:16
  • @ratsimihah, your timing code is still wrong. You want the first animation in your animation group to have a beginTime of 0, and the second animation to have a beginTime of the previous animation's duration, plus the desired pause time. You should not base beginTime on local layer time when an animation is part of a group. The begin time is zero-based in that case. – Duncan C Mar 20 '14 at 16:18
  • Ha! That latter sentence was the cause of my struggle. And I believe if I don't set beginTime for the first animation it's implicitly 0, isn't it? – ratsimihah Mar 20 '14 at 16:24
  • Yes, beginTime defaults to 0, which is usually what you want, and stands for "now". – Duncan C Mar 20 '14 at 17:46