Unable to combine CAKeyframeAnimations for scale and translation

3.3k views Asked by At

I would like to effect the scale and translation of a layer's transform at the same time but only the translation animation runs. Here is the errant code...

Here I create the scale animation with the keypath to scale.y:

    self.mainImage.layer.anchorPoint = CGPointMake(0.5, 1);
    CAKeyframeAnimation *mainAnimationScale = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.y"];
    mainAnimationScale.duration             = 0.75;
    mainAnimationScale.repeatCount          = 0;
    mainAnimationScale.removedOnCompletion  = YES;
    mainAnimationScale.autoreverses         = NO;
    mainAnimationScale.fillMode             = kCAFillModeForwards;
    mainAnimationScale.values               = [NSArray arrayWithObjects:
                                          [NSNumber numberWithFloat:1.0],
                                          [NSNumber numberWithFloat:1.0],
                                          [NSNumber numberWithFloat:0.7],
                                          [NSNumber numberWithFloat:1.0],
                                          [NSNumber numberWithFloat:0.85],
                                          [NSNumber numberWithFloat:1.0],
                                           nil];
    mainAnimationScale.keyTimes             = [NSArray arrayWithObjects:
                                          [NSNumber numberWithFloat:0],
                                          [NSNumber numberWithFloat:0.65],
                                          [NSNumber numberWithFloat:0.72],
                                          [NSNumber numberWithFloat:0.84],
                                          [NSNumber numberWithFloat:0.91],
                                          [NSNumber numberWithFloat:1],
                                           nil];

Here I create the translation animation with the keypath to translation.y:

    CAKeyframeAnimation *mainAnimationTrans = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.y"];
    mainAnimationTrans.duration             = 0.75;
    mainAnimationTrans.repeatCount          = 0;
    mainAnimationTrans.removedOnCompletion  = YES;
    mainAnimationTrans.autoreverses         = NO;
    mainAnimationTrans.fillMode             = kCAFillModeForwards;
    mainAnimationTrans.values               = [NSArray arrayWithObjects:
                                         [NSNumber numberWithFloat:-250.0],
                                         [NSNumber numberWithFloat:-250.0],
                                         [NSNumber numberWithFloat:0],
                                           nil];
    mainAnimationTrans.keyTimes             = [NSArray arrayWithObjects:
                                          [NSNumber numberWithFloat:0],
                                          [NSNumber numberWithFloat:0.65],
                                          [NSNumber numberWithFloat:1],
                                           nil];

Here I group them together and apply them to the UIView's layer:

    CAAnimationGroup *theGroup = [CAAnimationGroup animation];
    theGroup.duration = 0.75;
    theGroup.repeatCount = 0;
    theGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    theGroup.animations = [NSArray arrayWithObjects:mainAnimationTrans, mainAnimationScale, nil];
    [self.mainImage.layer addAnimation:theGroup forKey:@"layerAnimation"];

I initially tried adding the animations directly to the layer like this, but that didn't work either:

    [self.mainImage.layer addAnimation:mainAnimationTrans forKey:@"transform.scale.y"];
    [self.mainImage.layer addAnimation:mainAnimationTrans forKey:@"transform.translation.y"];

I've tried every string I can think for for the keyPath values and I'm guessing it has something to do with the fact that I'm animating two aspects of the transform, but I thought that using the group would fix that. Alas...

2

There are 2 answers

0
leebert On BEST ANSWER

The key to making this work is combining transforms. I don't know why I didn't realize this sooner since it is basic Matrix math...I think my co-worker even mentioned it yesterday. Ack.

At any rate, the animation I was going for was to have an item drop down and bounce once it reached its destination. Here is the code:

    self.mainImage.layer.anchorPoint = CGPointMake(0.3, 1.0);
    CAKeyframeAnimation *mainImageAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
    mainImageAnimation.duration             = 0.75;
    mainImageAnimation.repeatCount          = 0;
    mainImageAnimation.removedOnCompletion  = NO;
    mainImageAnimation.autoreverses         = NO;
    mainImageAnimation.fillMode             = kCAFillModeForwards;
    mainImageAnimation.values               = [NSArray arrayWithObjects:
                                         [NSValue valueWithCATransform3D:CATransform3DScale(CATransform3DMakeTranslation(-8, -250, 0), 1, 1, 1)],
                                         [NSValue valueWithCATransform3D:CATransform3DScale(CATransform3DMakeTranslation(-8, 17, 0), 1, 1, 1)],
                                         [NSValue valueWithCATransform3D:CATransform3DScale(CATransform3DMakeTranslation(-8, 17, 0), 1.2, .8, 1)],
                                         [NSValue valueWithCATransform3D:CATransform3DScale(CATransform3DMakeTranslation(-8, 17, 0), .85, 1.15, 1)],
                                         [NSValue valueWithCATransform3D:CATransform3DScale(CATransform3DMakeTranslation(-8, 17, 0), 1.1, .9, 1)],
                                         [NSValue valueWithCATransform3D:CATransform3DScale(CATransform3DMakeTranslation(-8, 17, 0), 1, 1, 1)],
                                           nil];
    mainImageAnimation.keyTimes             = [NSArray arrayWithObjects:
                                          [NSNumber numberWithFloat:0],
                                          [NSNumber numberWithFloat:0.6],
                                          [NSNumber numberWithFloat:0.65],
                                          [NSNumber numberWithFloat:0.78],
                                          [NSNumber numberWithFloat:0.88],
                                          [NSNumber numberWithFloat:1],
                                           nil];
    mainImageAnimation.timingFunctions      = [NSArray arrayWithObjects:
                                         [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
                                         [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
                                         [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
                                         [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
                                         [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
                                         [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
                                           nil];
    [self.mainImage.layer addAnimation:mainImageAnimation forKey:@"transform"];

Things to note:

  • To effect multiple transform properties combine the individual transforms by feeding the results of a CATransform3DMakeXXX function to the transform argument of a CATransform3DXXX function.
  • With Matrix math the order of the transforms matters: the last thing you feed to the matrix will be the first thing applied. In my case I want translation to be applied before scale so I first have to create a scale transform and apply it to the subsequent translation transform.
  • Altering a layer's anchorPoint changes the layer's position. In my case I needed the ancor point to be asymmetrical and you can see how I account for that change in position in the translation transform: It's resting place is not 0,0 but -8,17.

Hope this helps!

0
stevethomp On

Different to the question, but related, I was attempting to combine two CAKeyFrameAnimations, rotation and scale.

CAKeyframeAnimation *wiggle = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];
wiggle.values   = @[ @0.0, @-0.15, @0.0, @0.15, @-0.15, @0.0];
wiggle.keyTimes = @[ @0.0, @0.2,  @0.4, @0.6, @0.8,  @1.0];
wiggle.duration = 0.5;

CAKeyframeAnimation *resize = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
resize.duration = 0.5;
resize.values = @[@1.0, @1.5, @1.0];
resize.keyTimes = @[@0.0, @0.5, @1.0];

Adding the animations individually did not work, since only the rotation was happening.

[self.layer addAnimation:wiggle forKey:kAnimationKey];
[self.layer addAnimation:resize forKey:kAnimationKey];

But it worked when I created an animation group that contained both CAKeyFrameAnimations.

CAAnimationGroup *animGroup = [[CAAnimationGroup alloc] init];
animGroup.animations = @[wiggle, resize];
animGroup.duration = 0.5;

[self.layer addAnimation:animGroup forKey:kAnimationKey];