Note that there are some explanatory texts on larger screens.

plurals
  1. POHow do i rotate a CALayer around a diagonal line?
    text
    copied!<p>I'm trying to implement a flip animation to be used in board game like iPhone-application. The animation is supposed to look like a game piece that rotates and changes to the color of its back (kind of like an <a href="http://en.wikipedia.org/wiki/Reversi" rel="noreferrer">Reversi piece</a>). I've managed to create an animation that flips the piece around its orthogonal axis, but when I try to flip it around a diagonal axis by changing the rotation around the z-axis the actual image also gets rotated (not surprisingly). Instead I would like to rotate the image "as is" around a diagonal axis.</p> <p>I have tried to change <code>layer.sublayerTransform</code> but with no success.</p> <p>Here is my current implementation. It works by doing a trick to resolve the issue of getting a mirrored image at the end of the animation. The solution is to not actually rotate the layer 180 degrees, instead it rotates it 90 degrees, changes image and then rotates it back.</p> <p><strong>Final version:</strong> Based on Lorenzos suggestion to create a discrete keyed animation and calculate the transformation matrix for each frame. This version instead tries to estimate number of "guiding" frames needed based on the layer size and then uses a linear keyed animation. This version rotates with a arbitrary angle so to rotate around diagonal line use a 45 degree angle.</p> <p>Example usage:</p> <pre><code>[someclass flipLayer:layer image:image angle:M_PI/4] </code></pre> <p>Implementation:</p> <pre><code>- (void)animationDidStop:(CAAnimationGroup *)animation finished:(BOOL)finished { CALayer *layer = [animation valueForKey:@"layer"]; if([[animation valueForKey:@"name"] isEqual:@"fadeAnimation"]) { /* code for another animation */ } else if([[animation valueForKey:@"name"] isEqual:@"flipAnimation"]) { layer.contents = [animation valueForKey:@"image"]; } [layer removeAllAnimations]; } - (void)flipLayer:(CALayer *)layer image:(CGImageRef)image angle:(float)angle { const float duration = 0.5f; CAKeyframeAnimation *rotate = [CAKeyframeAnimation animationWithKeyPath:@"transform"]; NSMutableArray *values = [[[NSMutableArray alloc] init] autorelease]; NSMutableArray *times = [[[NSMutableArray alloc] init] autorelease]; /* bigger layers need more "guiding" values */ int frames = MAX(layer.bounds.size.width, layer.bounds.size.height) / 2; int i; for (i = 0; i &lt; frames; i++) { /* create a scale value going from 1.0 to 0.1 to 1.0 */ float scale = MAX(fabs((float)(frames-i*2)/(frames - 1)), 0.1); CGAffineTransform t1, t2, t3; t1 = CGAffineTransformMakeRotation(angle); t2 = CGAffineTransformScale(t1, scale, 1.0f); t3 = CGAffineTransformRotate(t2, -angle); CATransform3D trans = CATransform3DMakeAffineTransform(t3); [values addObject:[NSValue valueWithCATransform3D:trans]]; [times addObject:[NSNumber numberWithFloat:(float)i/(frames - 1)]]; } rotate.values = values; rotate.keyTimes = times; rotate.duration = duration; rotate.calculationMode = kCAAnimationLinear; CAKeyframeAnimation *replace = [CAKeyframeAnimation animationWithKeyPath:@"contents"]; replace.duration = duration / 2; replace.beginTime = duration / 2; replace.values = [NSArray arrayWithObjects:(id)image, nil]; replace.keyTimes = [NSArray arrayWithObjects: [NSNumber numberWithDouble:0.0f], nil]; replace.calculationMode = kCAAnimationDiscrete; CAAnimationGroup *group = [CAAnimationGroup animation]; group.duration = duration; group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; group.animations = [NSArray arrayWithObjects:rotate, replace, nil]; group.delegate = self; group.removedOnCompletion = NO; group.fillMode = kCAFillModeForwards; [group setValue:@"flipAnimation" forKey:@"name"]; [group setValue:layer forKey:@"layer"]; [group setValue:(id)image forKey:@"image"]; [layer addAnimation:group forKey:nil]; } </code></pre> <p>Original code:</p> <pre><code>+ (void)flipLayer:(CALayer *)layer toImage:(CGImageRef)image withAngle:(double)angle { const float duration = 0.5f; CAKeyframeAnimation *diag = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"]; diag.duration = duration; diag.values = [NSArray arrayWithObjects: [NSNumber numberWithDouble:angle], [NSNumber numberWithDouble:0.0f], nil]; diag.keyTimes = [NSArray arrayWithObjects: [NSNumber numberWithDouble:0.0f], [NSNumber numberWithDouble:1.0f], nil]; diag.calculationMode = kCAAnimationDiscrete; CAKeyframeAnimation *flip = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.y"]; flip.duration = duration; flip.values = [NSArray arrayWithObjects: [NSNumber numberWithDouble:0.0f], [NSNumber numberWithDouble:M_PI / 2], [NSNumber numberWithDouble:0.0f], nil]; flip.keyTimes = [NSArray arrayWithObjects: [NSNumber numberWithDouble:0.0f], [NSNumber numberWithDouble:0.5f], [NSNumber numberWithDouble:1.0f], nil]; flip.calculationMode = kCAAnimationLinear; CAKeyframeAnimation *replace = [CAKeyframeAnimation animationWithKeyPath:@"contents"]; replace.duration = duration / 2; replace.beginTime = duration / 2; replace.values = [NSArray arrayWithObjects:(id)image, nil]; replace.keyTimes = [NSArray arrayWithObjects: [NSNumber numberWithDouble:0.0f], nil]; replace.calculationMode = kCAAnimationDiscrete; CAAnimationGroup *group = [CAAnimationGroup animation]; group.removedOnCompletion = NO; group.duration = duration; group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; group.animations = [NSArray arrayWithObjects:diag, flip, replace, nil]; group.fillMode = kCAFillModeForwards; [layer addAnimation:group forKey:nil]; } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload