How do i rotate a CALayer around a diagonal line? - iphone

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 Reversi piece). 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.
I have tried to change layer.sublayerTransform but with no success.
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.
Final version: 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.
Example usage:
[someclass flipLayer:layer image:image angle:M_PI/4]
Implementation:
- (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 < 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];
}
Original 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];
}

you can fake it this way: create an affine transform that collapse the layer along it's diagonal:
A-----B B
| | /
| | -> A&D
| | /
C-----D C
change the image, and trasform the CALayer back in another animation.
This will create the illusion of the layer rotating around its diagonal.
the matrix for that should be if I remember math correctly:
0.5 0.5 0
0.5 0.5 0
0 0 1
Update:
ok, CA doen't really likes to use degenerate transforms, but you can approximate it this way:
CGAffineTransform t1 = CGAffineTransformMakeRotation(M_PI/4.0f);
CGAffineTransform t2 = CGAffineTransformScale(t1, 0.001f, 1.0f);
CGAffineTransform t3 = CGAffineTransformRotate(t2,-M_PI/4.0f);
in my tests on the simulator there still was a problem because the rotations happens faster than te translation so with a solid black square the effect was a bit weird. I suppose that if you have a centered sprite with transparent area around it the effect will be close to what expected. You can then tweak the value of the t3 matrix to see if you get a more appealing result.
after more research, it appears that one should animate it's own transition via keyframes to obtaim the maximum control of the transition itself. say you were to display this animation in a second, you should make ten matrix to be shown at each tenth of a second withouot interpolation using kCAAnimationDiscrete; those matrix can be generated via the code below:
CGAffineTransform t1 = CGAffineTransformMakeRotation(M_PI/4.0f);
CGAffineTransform t2 = CGAffineTransformScale(t1, animationStepValue, 1.0f);
CGAffineTransform t3 = CGAffineTransformRotate(t2,-M_PI/4.0f);
where animationStepValue for ech of the keyFrame is taken from this progression:
{1 0.7 0.5 0.3 0.1 0.3 0.5 0.7 1}
that is: you're generating ten different transformation matrix (actually 9), pushing them as keyframes to be shown at each tenth of a second, and then using the "don't interpolate" parameter. you can tweak the animation number for balancing smoothness and performance*
*sorry for possible errors, this last part was written without a spellchecker.

I got it solved. You probably already have a solution as well, but here is what I have found. It is quite simple really...
You can use a CABasicAnimation to do the diagonal rotation, but it needs to be the concatenation of two matrices, namely the existing matrix of the layer, plus a CATransform3DRotate. The "trick" is, in the 3DRotate you need to specify the coordinates to rotate around.
The code looks something like this:
CATransform3DConcat(theLayer.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0));
This will make a rotation that appears as though the upper-left corner of the square is rotating around the axis Y=X, and travelling to the lower-right corner.
The code to animate looks like this:
CABasicAnimation *ani1 = [CABasicAnimation animationWithKeyPath:#"transform"];
// set self as the delegate so you can implement (void)animationDidStop:finished: to handle anything you might want to do upon completion
[ani1 setDelegate:self];
// set the duration of the animation - a float
[ani1 setDuration:dur];
// set the animation's "toValue" which MUST be wrapped in an NSValue instance (except special cases such as colors)
ani1.toValue = [NSValue valueWithCATransform3D:CATransform3DConcat(theLayer.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0))];
// give the animation a name so you can check it in the animationDidStop:finished: method
[ani1 setValue:#"shrink" forKey:#"name"];
// finally, apply the animation
[theLayer addAnimation:ani1 forKey#"arbitraryKey"];
That's it! That code will rotate the square (theLayer) to invisibility as it travels 90-degrees and presents itself orthogonally to the screen. You can then change the color, and do the exact same animation to bring it back around. The same animation works because we are concatenating the matrices, so each time you want to rotate, just do this twice, or change M_PI/2 to M_PI.
Lastly, it should be noted, and this drove me nuts, that upon completion, the layer will snap back to its original state unless you explicitly set it to the end-animation state. This means, just before the line [theLayer addAnimation:ani1 forKey#"arbitraryKey"]; you will want to add
theLayer.transform = CATransform3DConcat(v.theSquare.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0));
to set its value for after the animation completes. This will prevent the snapping back to original state.
Hope this helps. If not you then perhaps someone else who was banging their head against the wall like we were! :)
Cheers,
Chris

Here is a Xamarin iOS example I use to flap the corner of a square button, like a dog ear (easily ported to obj-c):
Method 1: use a rotation animation with 1 for both x and y axes (examples in Xamarin.iOS, but easily portable to obj-c):
// add to the UIView subclass you wish to rotate, where necessary
AnimateNotify(0.10, 0, UIViewAnimationOptions.CurveEaseOut | UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.BeginFromCurrentState, () =>
{
// note the final 3 params indicate "rotate around x&y axes, but not z"
var transf = CATransform3D.MakeRotation(-1 * (nfloat)Math.PI / 4, 1, 1, 0);
transf.m34 = 1.0f / -500;
Layer.Transform = transf;
}, null);
Method 2: just add an x-axis rotation, and y-axis rotation to a CAAnimationGroup so they run at the same time:
// add to the UIView subclass you wish to rotate, where necessary
AnimateNotify(1.0, 0, UIViewAnimationOptions.CurveEaseOut | UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.BeginFromCurrentState, () =>
{
nfloat angleTo = -1 * (nfloat)Math.PI / 4;
nfloat angleFrom = 0.0f ;
string animKey = "rotate";
// y-axis rotation
var anim = new CABasicAnimation();
anim.KeyPath = "transform.rotation.y";
anim.AutoReverses = false;
anim.Duration = 0.1f;
anim.From = new NSNumber(angleFrom);
anim.To = new NSNumber(angleTo);
// x-axis rotation
var animX = new CABasicAnimation();
animX.KeyPath = "transform.rotation.x";
animX.AutoReverses = false;
animX.Duration = 0.1f;
animX.From = new NSNumber(angleFrom);
animX.To = new NSNumber(angleTo);
// add both rotations to a group, to run simultaneously
var animGroup = new CAAnimationGroup();
animGroup.Duration = 0.1f;
animGroup.AutoReverses = false;
animGroup.Animations = new CAAnimation[] {anim, animX};
Layer.AddAnimation(animGroup, animKey);
// add perspective
var transf = CATransform3D.Identity;
transf.m34 = 1.0f / 500;
Layer.Transform = transf;
}, null);

Related

iOS; How to scale UIimageView (permanently) and then move it

I've hit a wall here. I know how to move an Image using "CGAffineTransformMakeTranslation" and I also know how to scale an image using"CGAffineTransformMakeScale" but for the life of me, I can't seem to get one Image to do both of these and stay that way. It scales to the desired size for about a split second and then immediately reverts to its original size and moves to the desired location. What I need is for the image to get big, STAY big, and then move to a new location (while permanently staying its new size).
Here is what I've got going on in my .m file:
-(IBAction)PushZoomButton {
[UIWindow animateWithDuration:1.5
animations:^{
JustinFrame.transform = CGAffineTransformMakeScale(2.0, 2.0);
JustinFrame.transform = CGAffineTransformMakeTranslation(10.0, 10.0);}];
[UIWindow commitAnimations];}
Any help with this would be appreciated!
you can use CGAffineTransformConcat, for instance:
JustinFrame.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(2.0, 2.0), CGAffineTransformMakeTranslation(10.0, 10.0));
You may need to adapt the translation to (5, 5) since you have doubled the scale
The second transform you set overrides the first one. You need to concat both transform actions into one, as Luis said. Another way of writing that would be:
CGAffineTransform transform = CGAffineTransformMakeScale(2.0, 2.0);
transform = CGAffineTransformTranslate(transform, 10, 10);
JustinFrame.transform = transform;
You may need to look into CoreAnimation, basically what UIView animation is controlling under the hood. If you set up a CAAnimation, then what you want to achieve is done with the fillMode property of the animation.
Here's some example code to make a UIView look like it's opening like a door (copy pasted some code I have, but perhaps you could modify it and find it useful):
- (void) pageOpenView:(UIView *)viewToOpen duration:(NSTimeInterval)duration pageTurnDirection:(PageTurnDirection) p{
// Remove existing animations before stating new animation
[viewToOpen.layer removeAllAnimations];
// Make sure view is visible
viewToOpen.hidden = NO;
// disable the view so it’s not doing anythign while animating
viewToOpen.userInteractionEnabled = NO;
float dir = p == 0 ? -1.0f : 1.0f; // for direction calculations
// create an animation to hold the page turning
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
transformAnimation.removedOnCompletion = NO;
transformAnimation.duration = duration;
transformAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
CATransform3D startTransform = CATransform3DIdentity;
if (p == NEXT_PAGE) {
// orig values
startTransform.m34 = 0.001f;
}else {
// orig values
startTransform.m34 = -0.001f;
}
// start the animation from the current state
transformAnimation.fromValue = [NSValue valueWithCATransform3D:startTransform];
// this is the basic rotation by 90 degree along the y-axis
CATransform3D endTransform = CATransform3DMakeRotation(3.141f/2.0f,
0.0f,
dir,
0.0f);
// these values control the 3D projection outlook
if (p == NEXT_PAGE) {
endTransform.m34 = 0.001f;
endTransform.m14 = -0.0015f;
}else {
endTransform.m34 = -0.001f;
endTransform.m14 = 0.0015f;
}
transformAnimation.toValue = [NSValue valueWithCATransform3D:endTransform];
// Create an animation group to hold the rotation
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
// Set self as the delegate to receive notification when the animation finishes
theGroup.delegate = self;
theGroup.duration = duration;
// CAAnimation-objects support arbitrary Key-Value pairs, we add the UIView tag
// to identify the animation later when it finishes
[theGroup setValue:[NSNumber numberWithInt:[(BODBookPageView *)viewToOpen pageNum]] forKey:#"animateViewPageNum"]; //STEPHEN: We set the tag to the page number
[theGroup setValue:[NSNumber numberWithInt: p] forKey:#"PageTurnDirection"];
[theGroup setValue:[NSNumber numberWithBool:YES] forKey:#"isAnimationMidpoint"]; // i.e. is this the first half of page-turning or not?
// Here you could add other animations to the array
theGroup.animations = [NSArray arrayWithObjects:transformAnimation, nil];
theGroup.removedOnCompletion = NO; // THIS LINE AND THE LINE BELOW WERE CRUCIAL TO GET RID OF A VERY HARD TO FIND/FIX BUG.
theGroup.fillMode = kCAFillModeForwards; // THIS MEANS THE ANIMATION LAYER WILL STAY IN THE STATE THE ANIMATION ENDED IN, THEREBY PREVENTING THAT ONE FRAME FLICKER BUG.
// Add the animation group to the layer
[viewToOpen.layer addAnimation:theGroup forKey:#"flipViewOpen"];
}

How do I animate CATransform3Ds with a CAKeyframeAnimation?

I've used CAKeyframeAnimations to animate a layer's transform.rotation and transform.translation.x properties, but I'm having trouble animating the transform property implicitly. I have a layer that must animate between two states and CABasicAnimation's default interpolation is totally incorrect and doesn't follow the path I want. CAKeyframeAnimation to the rescue, or so I thought. Any attempt to animate transform using a CAKeyframeAnimation results in the view immediately snapping to the final transform while the other animations run. If I remove the first half of the following function and let my "transform" events use the CABasicAnimation on the bottom, it animates just fine - albeit with incorrectly interpolated transforms along the way.
My layer delegate has implemented the following:
- (id <CAAction>) actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
if ([event isEqualToString:#"transform"])
{
CGSize startSize = ((CALayer *)self.layer.presentationLayer).bounds.size;
CGSize endSize = self.layer.bounds.size;
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:event];
animation.duration = 0.25;
NSMutableArray *values = [NSMutableArray array];
int stepCount = 10;
for (int i = 0; i < stepCount; i++)
{
CGFloat p = i / (float)(stepCount - 1);
CGSize size = [self interpolateBetweenSize:startSize andSize:endSize percentage:p];
CATransform3D transform = [self transformForSize:size];
[values addObject:[NSValue valueWithCATransform3D:transform]];
}
animation.values = values;
return animation;
}
// All other animations use this basic animation
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:event];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.removedOnCompletion = YES;
animation.fillMode = kCAFillModeForwards;
animation.duration = 0.25;
return animation;
}
My transform is a translation followed by a rotate, but I think a group animation with separate keyframe animations animating through a translation AND a rotate would result in crazy town. I've confirmed that size & transform are correct for all values of p that I pass though, and p strictly ranges from 0 to 1.
I've tried setting a non-default timing function, I've tried setting an array of timing functions, I've omitted the keyTImes, I've set a repeatCount of 0, removedOnCompletion=YES, and a fillMode of forwards and that had no effect. Am I not creating the transform keyframe animation correctly?
This technique worked back in iOS 3 but seemed to be broken in iOS 5.0.
5.1 'magically' fixed this, it seemed to be a bug in iOS 5.0. I'd file a radar, but it is now working in 5.1.
#Gsnyder: Some background: I am experimenting with Clear-like UI (for something completely unrelated to Clear) and came up with this: http://blog.massivehealth.com/post/18563684407/clear. That should explain the need for a rotate & translate.
I've since created a shutter transition that subdivides a view into N layers (instead of just 2) that looks like this when viewed from the side: /////.
My code is not animating the bounds, it is using the size at each step to determine the necessary transform.
#Paul.s: Implicit allows me to keep this abstraction within the layer class itself without polluting the view controller that owns it. The view controller should just be changing the bounds around and the layer should move appropriately. I'm not a fan of view controllers having dozens of custom animations when the views themselves can handle it.
I need to use a keyframe animation because the default animation between layer transforms / and _ animate through incorrect angles so the ///\ layers do not line up throughout the transform. The keyframe animations ensure the edges all line up correctly while they all animate.
I'm considering this closed, this seems to be a bug in iOS 5.0 and has since been fixed. Thanks everyone.
(void)animateViewWith3DCurrentView:(UIView *)currentView withPoing:(CGPoint)movePoint
{
//flip the view by 180 degrees in its place first.
currentView.layer.transform = CATransform3DRotate(currentView.layer.transform,myRotationAngle(180), 0, 1, 0);
//set the anchor point so that the view rotates on one of its sides.
currentView.layer.anchorPoint = CGPointMake(0.5, 0.5);
//Set up scaling
CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:kResizeKey];
//we are going to fill the screen here. So 423,337
[resizeAnimation setToValue:[NSValue valueWithCGSize:CGSizeMake(423, 337)]];
resizeAnimation.fillMode = kCAFillModeForwards;
resizeAnimation.removedOnCompletion = NO;
// Set up path movement
UIBezierPath *movePath = [UIBezierPath bezierPath];
//the control point is now set to centre of the filled screen. Change this to make the path different.
// CGPoint ctlPoint = CGPointMake(0.0, 0.5);
CGPoint ctlPoint = CGPointMake(1024/2, 768/2);
//This is the starting point of the animation. This should ideally be a function of the frame of the view to be animated. Hardcoded here.
// Set here to get the accurate point..
[movePath moveToPoint:movePoint];
//The anchor point is going to end up here at the end of the animation.
[movePath addQuadCurveToPoint:CGPointMake(1024/2, 768/2) controlPoint:ctlPoint];
CAKeyframeAnimation *moveAnim = [CAKeyframeAnimation animationWithKeyPath:kPathMovement];
moveAnim.path = movePath.CGPath;
moveAnim.removedOnCompletion = YES;
// Setup rotation animation
CABasicAnimation* rotateAnimation = [CABasicAnimation animationWithKeyPath:kRotation];
//start from 180 degrees (done in 1st line)
CATransform3D fromTransform = CATransform3DMakeRotation(myRotationAngle(180), 0, 1, 0);
//come back to 0 degrees
CATransform3D toTransform = CATransform3DMakeRotation(myRotationAngle(0), 0, 1, 0);
//This is done to get some perspective.
CATransform3D persp1 = CATransform3DIdentity;
persp1.m34 = 1.0 / -3000;
fromTransform = CATransform3DConcat(fromTransform, persp1);
toTransform = CATransform3DConcat(toTransform,persp1);
rotateAnimation.toValue = [NSValue valueWithCATransform3D:toTransform];
rotateAnimation.fromValue = [NSValue valueWithCATransform3D:fromTransform];
//rotateAnimation.duration = 2;
rotateAnimation.fillMode = kCAFillModeForwards;
rotateAnimation.removedOnCompletion = NO;
// Setup and add all animations to the group
CAAnimationGroup *group = [CAAnimationGroup animation];
[group setAnimations:[NSArray arrayWithObjects:moveAnim,rotateAnimation, resizeAnimation, nil]];
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
group.duration = 0.7f;
group.delegate = self;
[group setValue:currentView forKey:kGroupAnimation];
[currentView.layer addAnimation:group forKey:kLayerAnimation];
}

Spin UIImageView continuously

I am having a problem while trying to rotate UIImageview continuously with a ball's image inside. I would like this ball to spin continuously on its center axis.
I have tried using CGAffineTransform but it didn't work.
Please help!
This may be an old Q but it's near the top of search results for the topic. Here's a more cut and dry solution: (make sure to import QuartzCore/QuartzCore.h)
CABasicAnimation *rotation;
rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotation.fromValue = [NSNumber numberWithFloat:0];
rotation.toValue = [NSNumber numberWithFloat:(2*M_PI)];
rotation.duration = 1.1; // Speed
rotation.repeatCount = HUGE_VALF; // Repeat forever. Can be a finite number.
[yourView.layer addAnimation:rotation forKey:#"Spin"];
Then, to stop remove/reset the animation: (see comments for how to stop-in-place)
[yourView.layer removeAnimationForKey:#"Spin"];
In swift:
let rotation = CABasicAnimation(keyPath: "transform.rotation")
rotation.fromValue = 0
rotation.toValue = 2 * M_PI
rotation.duration = 1.1
rotation.repeatCount = Float.infinity
view.layer.addAnimation(rotation, forKey: "Spin")
It should work if you use transforms as:
itemToRotate.transform = CGAffineTransformRotate(itemToRotate.transform, currentAngle);
I've uploaded some sample code of a working solution. Add your logic to rotate it automatically...

Rotate UIView on a Pivot

Please could you tell me how to rotate a UIView (with a pie chart on) as if its on a pivot. It will only rotate once the user has flicked the view.
You'd use something like this:
- (void)spin
{
CABasicAnimation *fullRotation;
fullRotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
fullRotation.fromValue = [NSNumber numberWithFloat:0];
fullRotation.toValue = [NSNumber numberWithFloat:M_PI * 360 / 180.0];
fullRotation.duration = 0.25;
fullRotation.repeatCount = 1;
[self.view.layer addAnimation:fullRotation forKey:#"360"];
}
Merely call the spin method when you want to spin 360º. Adjust as needed to spin more, less, faster, slower, etc.
EDIT: Should note, that in the above code, the view property is the one we're rotating, in case it wasn't obvious. So change self.view to whatever view you want to rotate.

After rotating a CALayer using CABasicAnimation the layer jumps back to it's unrotated position

I am trying to create a falling coin. The coin image is a CALayer with 2 CABasicAnimations on it - a falling down and a rotation one. When the falling down animation gets to its end, it stays there. The rotation animation though, which is supposed to be random and end up in a different angle every time, just pops back to the original CALAyer image.
I want it to stay in the angle it finished the animation on. Is it possible? How do I do it?
Code:
//Moving down animation:
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform.translation.y"];
anim.duration = 1;
anim.autoreverses = NO;
anim.removedOnCompletion = YES;
anim.fromValue = [NSNumber numberWithInt: -80 - row_height * (8 - _col)];
anim.toValue = [NSNumber numberWithInt: 0];
//Rotation Animation:
CABasicAnimation *rota = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rota.duration = 4;
rota.autoreverses = NO;
rota.removedOnCompletion = NO;
rota.fromValue = [NSNumber numberWithFloat: 0];
rota.toValue = [NSNumber numberWithFloat: 2.5 * 3.15 ];
[cl addAnimation: rota forKey: #"rotation"];
[cl addAnimation: anim forKey: #"animateFalling"];
Have you set the removedOnCompletion property of the rotation animation to NO, e.g.,
rota.removedOnCompletion = NO;
That should leave the presentation layer where it was when the animation finished. The default is YES, which will snap back to the model value, i.e., the behavior you describe.
The fillMode should also be set, i.e.,
rota.fillMode = kCAFillModeForwards;
I was trying to rotate an arrow back and forth, like the Twitter/Facebook "Pull to Refresh" effect.
The problem is, I was doing the rotation back and forth on the same UIView, so after adding
rotation.removedOnCompletion = NO;
rotation.fillMode = kCAFillModeForwards;
The forward animation war working OK but the backwards animation was not working at all.
So I added the last line suggested by yeahdixon, and in addition set the view's transform to the animation's completed state: (rotation by 180 degrees)
[myview.layer removeAllAnimations];
myView.transform = CGAffineTransformMakeRotation(M_PI);
For the 'restore' animation (backwards) I do this on completion:
myView.transform = CGAffineTransformMakeRotation(0);
...and it works fine. Somehow it doesn't need the removeAllAnimations call.
I found that by setting : removedOnCompletion = NO;
did not produce a visible leak in instruments, but did not get deallocated and was accumulating a small amount of memory. IDK if its my implementation or what, but by adding removeAllAnimations at the end of the animation seemed to clear out this tiny bit of residual memory.
[myview.layer removeAllAnimations];