Basic keyframe animation (rotation) - iphone

I'm trying to create a very simple keyframe animation, whereby a graphic Rotates from one angle to another, through a given midpoint.
(The purpose is to be able to animate rotation through an OBTUSE angle of arc GREATER THAN 180 DEGREES, rather than having the animation 'cheat' and go the shortest route, i.e., via the opposite , ACUTE smaller angle -- which can happen when there's only one [i.e, destination] keyframe. To go the 'long' way around, I assume I need an extra keyframe midway through, along the desired arc.)
Here's what I've got so far (which does get the graphic to the desired rotation, via the most acute angle):
#define DEGREES_TO_RADIANS(__ANGLE__) ((__ANGLE__) / 180.0 * M_PI)
...
[UIView beginAnimations:nil context:nil];
CGAffineTransform cgCTM = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(desiredEndingAngle));
[UIView setAnimationDuration:0.5];
graphic.transform = cgCTM;
[UIView commitAnimations];
As I understand it, I’m not looking for animation along a Path (since that’s for Translation, rather than Rotation) ...
Anyway, any help would be VERY much appreciated! Thanks in advance.

Think I’ve got it.
Here’s code which does (in this example) a full 270 DEGREE rotation (1.5*pi radians), including various parameters that can be customized further:
CALayer *layer = rotatingImage.layer;
CAKeyframeAnimation *animation;
animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.duration = 0.5f;
animation.cumulative = YES;
animation.repeatCount = 1;
animation.values = [NSArray arrayWithObjects: // i.e., Rotation values for the 3 keyframes, in RADIANS
[NSNumber numberWithFloat:0.0 * M_PI],
[NSNumber numberWithFloat:0.75 * M_PI],
[NSNumber numberWithFloat:1.5 * M_PI], nil];
animation.keyTimes = [NSArray arrayWithObjects: // Relative timing values for the 3 keyframes
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:.5],
[NSNumber numberWithFloat:1.0], nil];
animation.timingFunctions = [NSArray arrayWithObjects:
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], // from keyframe 1 to keyframe 2
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], nil]; // from keyframe 2 to keyframe 3
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:animation forKey:nil];
Thanks!

Try this:
UIImageView* rotatingImage = [[UIImageView alloc] init]];
[rotatingImage setImage:[UIImage imageNamed:#"someImage.png"]];
CATransform3D rotationTransform = CATransform3DMakeRotation(1.0f * M_PI, 0, 0, 1.0);
CABasicAnimation* rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
rotationAnimation.toValue = [NSValue valueWithCATransform3D:rotationTransform];
rotationAnimation.duration = 0.25f;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = 1;
[rotatingImage.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];

CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 100, 100);
CGPathAddQuadCurveToPoint(path, NULL, 100, 100, 100, 615);
CGPathAddQuadCurveToPoint(path, NULL, 100, 615, 900, 615);
CGPathAddQuadCurveToPoint(path, NULL, 900, 615, 900, 100);
CGPathAddQuadCurveToPoint(path, NULL, 900, 100, 100, 80);
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.path = path;
pathAnimation.duration = 10.0;
[someLayer addAnimation:pathAnimation forKey:nil];

Related

CAShapeLayer and animation position issue

I am making an iPhone app that draws a circle on the screen. I use a timer (every 5 second), so that I can 'grow' the circle, then wait 5 seconds then grow again. Now I am having trouble when animating the increase in size of the circle (I use paths and a CAShapeLayer layer). The size (from and to) are fine with the animation, its just when it starts the circle moves to the upper left hand side and grows from there. Any help or suggestions would be great. Thanks
The class where this is implemented is a UIControl, which is added to a UIView.
//Called in init method
(void) initalPop {
//Circle
self.bubble = [CAShapeLayer layer];
self.bubble.bounds = self.bounds;
self.bubble.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
self.bubble.fillColor = [
[UIColor greenColor] CGColor
];
self.bubble.strokeColor = [
[UIColor greenColor] CGColor
];
self.bubble.lineWidth = 4.0;
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddEllipseInRect(path, nil, self.bounds);
self.bubble.path = path;
[self.layer addSublayer: self.bubble];
}
//Called when timer goes off
- (void) StageGrow {
CGFloat growSize = 50.0;
//Change the size of us and center the circle (but dont want to animate this
[CATransaction begin];
[CATransaction setValue: (id) kCFBooleanTrue forKey: kCATransactionDisableActions];
self.bounds = CGRectMake(0, 0, self.bounds.size.width + growSize, self.bounds.size.height + growSize);
self.bubble.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
[CATransaction commit];
[self ActualGrowCircle];
} - (void) ActualGrowCircle {
CGMutablePathRef oldPath = CGPathCreateMutable();
CGPathAddEllipseInRect(oldPath, nil, self.bubble.bounds);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddEllipseInRect(path, nil, self.bounds);
CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath: #"path"];
animation.duration = 2.0;
animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];
animation.repeatCount = 1;
animation.delegate = self;
animation.autoreverses = NO;
animation.fromValue = (__bridge id) oldPath;
animation.toValue = (__bridge id) path;
self.bubble.bounds = self.bounds;
self.bubble.path = path;
[self.bubble addAnimation: animation forKey: #"animatePath"];
}
Let's say you're growing the bubble from size (100, 100) to size (150, 150).
You create oldPath as an ellipse in the rectangle (0, 0, 100, 100).
You create path as an ellipse in the rectangle (0, 0, 150, 150).
You set the bounds of the bubble layer to (0, 0, 150, 150), and you don't animate this change.
That means that oldPath will appear aligned to the top and left edges of the bubble layer.
One way to fix this is to create oldPath in a rectangle that is centered in the bubble's new bounds:
CGRect oldBubbleRect = CGRectInset(self.bounds, growSize / 2, growSize / 2);
CGPathRef oldPath = CGPathCreateWithEllipseInRect(oldBubbleRect, NULL);
By the way, you are leaking the paths you're creating. You need to call CGPathRelease on them after you're done with them.
Worked it out. In the long run I grouped the animations together and all became good (see code below). Thanks for your help Rob.
-(void)ActualGrowCircle {
CGMutablePathRef newPath = CGPathCreateMutable();
CGPathAddEllipseInRect(newPath, nil, self.bounds);
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.repeatCount = 1;
animation.autoreverses = NO;
animation.fromValue = (__bridge id)self.bubble.path;
animation.toValue = (__bridge id)newPath;
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:#"bounds"];
animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation2.repeatCount = 1;
animation2.autoreverses = NO;
animation2.fromValue = [NSValue valueWithCGRect:self.bubble.bounds];
animation2.toValue = [NSValue valueWithCGRect:self.bounds];
CAAnimationGroup *group = [CAAnimationGroup animation];
[group setAnimations:[NSArray arrayWithObjects: animation2, animation, nil]];
group.duration = 0.5;
group.delegate = self;
self.bubble.bounds = self.bounds;
self.bubble.path = newPath;
[self.bubble addAnimation:group forKey:#"animateGroup"];
CGPathRelease(newPath);
}

Nice zoom-fade transition between controllers like in new Facebook, Gmail apps

Recently more and more apps are using new type of transition between views.
It's hard to define it but the transition looks like the preview view is fading out, going lower (downward translation) and scale down a little - all simultanously.
It's really subtle and elegant.
You can observe this transition in slow motion in Facebook app - find somebody's picture on the wall, tap to view it and then drag the fullscreen image down slowly and you will notice that the wall is rising from the back - fading and scaling a little bit. That's the transition.
Transition also envolves statusbar fading.
This transition is also present in Gmail 2.0 when you open settings from the left pane.
I think there is a certain framework or prepared method for this because more and more apps have this implemented.
But maybe I'm also wrong because I see some minor differences in some apps in the transition's trajectory - eg. Gmail uses a little carousel effect, but facebook just scales down to the middle.
Anyway it appears to be a new trend.
I'm looking for some reference or framework or know-how about implementing that sort of transitions.
Thanks for any useful stuff.
Try this link.I think this is the thing you are exactly looking for https://github.com/kentnguyen/KNSemiModalViewController
I first saw it used in the National Geographic apps... have a try with these two methods, the first 'drops it back' the second 'brings it back'. It's worked well for me in the past, just make a few adjustments to suit your needs.
- (void)dropItBack:(id)sender
{
// Position
CABasicAnimation *posAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
posAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(160, 284)];//center point
// Opacity
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
opacityAnimation.toValue = [NSNumber numberWithFloat:0.5f];
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scaleAnimation.toValue = [NSNumber numberWithDouble:0.8];
// Dramaticism of the Z rotation
// A lower number is more dramatic
float distance = 1000;
CATransform3D trans = CATransform3DIdentity;
trans.m34 = 1.f / -distance;
// Rotate Back
CABasicAnimation *rockBack = [CABasicAnimation animationWithKeyPath:#"transform"];
rockBack.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(trans, M_PI_4, 1.f, 0.f, 0.f)];
rockBack.beginTime = 0.f;
// Rotate forward
trans.m34 = 0.f;
CABasicAnimation *rockForward = [CABasicAnimation animationWithKeyPath:#"transform"];
rockForward.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(trans, M_PI_4, -1.f, 0.f, 0.f)];
rockForward.beginTime = 0.25f;
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = 0.5f;
animationGroup.repeatCount = 0.f;
animationGroup.removedOnCompletion = NO;
animationGroup.autoreverses = NO;
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[animationGroup setAnimations:[NSArray arrayWithObjects:rockBack, rockForward, scaleAnimation, posAnimation, opacityAnimation, nil]];
[self.navigationController.view.layer addAnimation:animationGroup forKey:nil];
popsheet = [UIButton buttonWithType:UIButtonTypeCustom];
[popsheet setBackgroundColor:[UIColor blueColor]];
[popsheet setFrame:CGRectMake(0, 580, 320, 400)];
[popsheet addTarget:self action:#selector(bringItForward:) forControlEvents:UIControlEventTouchUpInside];
[self.tabBarController.view addSubview:popsheet];
[UIView animateWithDuration:0.3 animations:^{
[popsheet setFrame: CGRectMake(0, 180, 320, 400)];
}];
}
- (void)bringItForward:(id)sender
{
// Position
CABasicAnimation *posAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
posAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(160, 284)];
// Opacity
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
opacityAnimation.toValue = [NSNumber numberWithFloat:1.f];
// Scale
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scaleAnimation.toValue = [NSNumber numberWithDouble:1.f];
// Dramaticism of the Z rotation
// A lower number is more dramatic
float distance = 1000;
CATransform3D trans = CATransform3DIdentity;
trans.m34 = 1.f / distance;
// Rotate back
CABasicAnimation *rockBack = [CABasicAnimation animationWithKeyPath:#"transform"];
rockBack.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(trans, M_PI_4, 1.f, 0.f, 0.f)];
rockBack.beginTime = 0.f;
// Rotate forward
trans.m34 = 0.f;
CABasicAnimation *rockForward = [CABasicAnimation animationWithKeyPath:#"transform"];
rockForward.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(trans, M_PI_4, -1.f, 0.f, 0.f)];
rockForward.beginTime = 0.25f;
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = 0.5f;
animationGroup.repeatCount = 0.f;
animationGroup.removedOnCompletion = NO;
animationGroup.autoreverses = NO;
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[animationGroup setAnimations:[NSArray arrayWithObjects:posAnimation, rockBack, rockForward, opacityAnimation, nil]];
[self.navigationController.view.layer addAnimation:animationGroup forKey:nil];
[UIView animateWithDuration:0.3 animations:^{
[popsheet setFrame: CGRectMake(0, 580, 320, 400)];
}];
}
I think you should try this library:
https://github.com/michaelhenry/MHFacebookImageViewer

CATransform3DRotate rotate for 360 degrees

I've started using CATransform3D lately and it seems very nice. I just have 1 issue with the rotation though. I'm trying to rotate my view for 360˚ degrees to the right but if I just put pass 360 to CATransform3DRotate it doesn't work (It just doesn't move at all.)
Here's my code:
CALayer *layer = dock.layer;
CATransform3D r = CATransform3DIdentity;
r.m34 = 1.0 / -500;
r = CATransform3DRotate(r, DegreesToRadians(360.0f), 100.0f, 1.0f, 100.0f);
layer.transform = r;
Does anyone know how to fix this issue? Thanks in advance! :)
You can animate a layer through one (or more) full rotations around its Z axis by animating the layer's transform.rotation key path, like this:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.duration = .25;
animation.fromValue = [NSNumber numberWithFloat:0];
animation.toValue = [NSNumber numberWithFloat:2 * M_PI];
[layer addAnimation:animation forKey:animation.keyPath];
You can animate around the X or Y axes using the key paths transform.rotation.x and transform.rotation.y. (The transform.rotation.z key path has the same effect as the transform.rotation key path.) You can apply multiple rotations, on separate axes, simultaneously.
Another way to do it, which probably works better if you want to rotate around an off-axis vector, is using a keyframe animation, like this:
CALayer *layer = [sender layer];
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -50;
layer.transform = transform;
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
animation.values = [NSArray arrayWithObjects:
[NSValue valueWithCATransform3D:CATransform3DRotate(transform, 0 * M_PI / 2, 100, 1, 100)],
[NSValue valueWithCATransform3D:CATransform3DRotate(transform, 1 * M_PI / 2, 100, 1, 100)],
[NSValue valueWithCATransform3D:CATransform3DRotate(transform, 2 * M_PI / 2, 100, 1, 100)],
[NSValue valueWithCATransform3D:CATransform3DRotate(transform, 3 * M_PI / 2, 100, 1, 100)],
[NSValue valueWithCATransform3D:CATransform3DRotate(transform, 4 * M_PI / 2, 100, 1, 100)],
nil];
animation.duration = 2;
[layer addAnimation:animation forKey:animation.keyPath];

How to adjust drop shadow dynamically during an UIImageView rotation?

I use the following to code to add drop shadow:
letterE.layer.shadowColor = [[UIColor blackColor] CGColor];
letterE.layer.shadowOffset = CGSizeMake(2.5, 2.5);
letterE.layer.shadowRadius = 3.0;
letterE.layer.shadowOpacity = 0.95;
and the following to rotate:
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0];
rotationAnimation.duration = 5.0;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = 1.0;
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[letterE.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
During the animation, the shadow is static which looks weird:
How can I make the shadow dynamically updated during the animation?
I found this interesting so I gave it a shot. A possible solution is to build a second clear view under the main view, giving it (the bottom view) a shadow using the original view's path. Then you can apply the animation to both views. I did this with simple rectangular views but I see no reason why this can't be done with more complex paths or using 2 CALayers instead of 2 UIViews.
This is how I set up my views...
testView = [[UIView alloc] initWithFrame:CGRectMake(40, 40, 100, 100)];
testView.backgroundColor = [UIColor redColor];
//increase y origin of second view to simulate shadow offset
testViewShadow = [[UIView alloc] initWithFrame:CGRectMake(40, 50, 100, 100)];
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, testView.frame.size.width, testView.frame.size.height));
testViewShadow.layer.shadowPath = path;
testViewShadow.layer.shadowOpacity = 1.0;
testViewShadow.layer.shadowOffset = CGSizeMake(0, 0);
testViewShadow.layer.shadowRadius = 10.0;
CFRelease(path);
[self.view addSubview:testViewShadow];
[self.view addSubview:testView];
..and my animation (just hooked up your CAAnimation to a button as an IBAction and applied to both views)...
- (IBAction)rotate:(id)sender{
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0];
rotationAnimation.duration = 5.0;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = 1.0;
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[testView.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
[testViewShadow.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
This is what the result looks like...
Any (2D) transformations should look believable if you apply them to both views.
Hope that helps!

How can I enforce an specific direction (i.e. clockwise) of rotation in Core Animation?

I am rotating a view with this:
CGAffineTransform rotatedTransform = CGAffineTransformRotate(CGAffineTransformIdentity, rotationValue);
I have an object which I want to spin around for about 320 degrees. Now Core Animation is clever and just rotates it as much as needed, doing that by rotating it with -40 degrees. So the object rotates the other way around with a shorter amount of movement.
I want to constrain it to rotate clockwise. Would I have to do that by changing animations in little steps, or is there an more elegant way?
The following snippet rotates a view called someView by using key-framed animation. The animation consists of 3 frames spread over 1 second, with the view rotated to 0º, 180º and 360º in the first, second and last frames respectively. Code follows:
CALayer* layer = someView.layer;
CAKeyframeAnimation* animation;
animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.duration = 1.0;
animation.cumulative = YES;
animation.repeatCount = 1;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0 * M_PI],
[NSNumber numberWithFloat:0.5 * M_PI],
[NSNumber numberWithFloat:1.0 * M_PI], nil];
animation.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:1.0], nil];
animation.timingFunctions = [NSArray arrayWithObjects:
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear], nil];
[layer addAnimation:animation forKey:#"transform.rotation.z"];
If you're after counterclockwise animation, you should use negative values. For a slightly more basic animation, you can use CABasicAnimation:
CALayer* layer = someView.layer;
CABasicAnimation* animation;
animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.fromValue = [NSNumber numberWithFloat:0.0 * M_PI];
animation.toValue = [NSNumber numberWithFloat:1.0 * M_PI];
animation.duration = 1.0;
animation.cumulative = YES;
animation.repeatCount = 1;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:rotationAnimation forKey:#"transform.rotation.z"];
I believe you have to give it another "key frame" if you will to give Core Animation the hint that it needs to go that direction.
Make sure and turn off easing, (at least for the end/beginning of the middle step) otherwise the animation will not look smooth.