iOS rotation animation does not happen smoothly in CAAnimationGroup - iphone

I have 3 animations running in a group: position, scale and rotation
However my rotation animation does not happen smoothly. It happens abruptly in the beginning, while other animations happen smoothly.
Here is my code:
//CABasicAnimation *scaleAnimation
//CABasicAnimation *moveAnimation
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
rotationAnimation.autoreverses = NO;
rotationAnimation.toValue = [NSNumber numberWithFloat: 0];//-(M_PI/3)];
//below line shows the end state, if removed, the layer will assume its initial position after animation, something which I don't want.
[layer setTransform:CATransform3DMakeRotation(0, 0, 1, 0)];
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
//Code for adding all 3 animations to the group
Edit:
rotationAnimation.toValue = [NSNumber numberWithFloat: -(M_PI/3)];
Removed final transform value from the place of animation declaration, and added it to delegate. (note that in case of animation group, individual animation delegates do not get hit, only the group delegate worked).
Under delegate:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
NSString * animName = [theAnimation valueForKey:#"animationName"];
CALayer* layer = [theAnimation valueForKey:#"animationLayer"];
if ([animName isEqualToString:#"MoveZoomOut"])
{
if ([layer.name isEqualToString:#"Layer1"])
[layer setTransform:CATransform3DMakeRotation(-M_PI/3, 0, 1, 0)];
else if ([layer.name isEqualToString:#"Layer2"])
[layer setTransform:CATransform3DMakeRotation(M_PI/3, 0, 1, 0)];
else
[layer setTransform:CATransform3DMakeRotation(0, 0, 1, 0)];
}
if ([layer respondsToSelector:#selector(setContentsScale:)])
{
layer.contentsScale = [UIScreen mainScreen].scale;
}
}

I have 3 animations running in a group: position, scale and rotation
I suspect that since you are animating both scale and rotation, which is done through the transform property, there might be a clash.
Try to animate scale and rotation in a single animation by concatenating the 2 transforms into one:
CATransform3D t = CATransform3DMakeRotation(0, 0, 1, 0)];
[layer setTransform:CATransform3DScale(t, sx, sy, sz)];
EDIT:
I understand that what is happening is pretty unusual, so I will give you a couple of hints as to tricks that in several occasions helped me. I understand, you have 3 layers to animate.
wrap each layer animations in [CATransaction begin]/[CATransaction commit];
if that does not help,
do not set the final value of the property you are going to animate right after creating the animation itself; rather set it in the animation delegate animationDidStop:finished: method.
In other words, given your code:
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
rotationAnimation.autoreverses = NO;
rotationAnimation.toValue = [NSNumber numberWithFloat: 0];//-(M_PI/3)];
//below line shows the end state, if removed, the layer will assume its initial position after animation, something which I don't want.
[layer setTransform:CATransform3DMakeRotation(0, 0, 1, 0)];
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
don't do [layer setTransform:CATransform3DMakeRotation(0, 0, 1, 0)]; there. Rather, do it in:
- (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag {
[layer setTransform:CATransform3DMakeRotation(0, 0, 1, 0)];
}
Since you will have more than one animation going on (and presumably use the same delegate for all of them), you will need a way to tell which animation the animationDidStop: method refers to. To this aim, you could do, e.g.:
//-- se the delegate
rotationAnimation.delegate = self;
[rotationAnimation setValue:layer forKey:#"animationLayer"];
and in the delegate method:
- (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag {
[CATransaction begin];
[CATransaction setDisableActions:YES];
CALayer* layer = [anim valueForKey:#"animationLayer"];
layer.transform = ...;
[CATransaction commit];
}
I know that this seems a bit contrived. And it is. But it is the only way I could fix an issue similar to what you are reporting.
Hope this helps.
EDIT:
For the glitch when using animationDidStop:, try to wrap animationDidStop: content in:
[CATransaction begin];
[CATransaction setDisableActions:YES];
...
[CATransaction commit];

Related

how to increase and decrease the image alpha value with animation in ios sdk?

In this code i am using animation.But at a time i need to change the image alpha value also.
-(void)animation
{
CGPoint point = CGPointMake(imgView.frame.origin.x, imgView.frame.origin.y);
imgView.layer.position = point;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"position.x"];
anim.fromValue = [NSValue valueWithCGPoint:point];
anim.toValue = [NSValue valueWithCGPoint:CGPointMake(point.x + 50, point.y)];
anim.duration = 10.0f;
anim.repeatCount =1;
anim.removedOnCompletion = YES;
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[imgView.layer addAnimation:anim forKey:#"position.x"];
imgView.layer.position = point;
}
You can use the opacity key of CABasicAnimation for doing this:
CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
alphaAnimation.fromValue = [NSNumber numberWithFloat:1.0];
alphaAnimation.toValue = [NSNumber numberWithFloat:0.0];
[imgView.layer addAnimation:alphaAnimation forKey:#"opacity"];
Edit:
For animating through specified values you can use CAKeyframeAnimation:
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"opacity"];
animation.duration = 10.0f;
animation.repeatCount = 1;
animation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:1.0,
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:1.0],nil];
animation.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:5.0],
[NSNumber numberWithFloat:10.0], nil];
[imgView.layer addAnimation:animation forKey:#"opacity"];
In order to decrease the alpha as the same time/rate the x position of your view is moving, just put the position setting code together with the alpha setting code in the same block of animation, like below:
CGPoint finalPoint = CGPointMake(500, imgView.center.y); //change for any final point you want
CGFloat finalAlpha = 0.0; //change the final alpha as you want
UIView animateWithDuration:1.0 animations:^{ //change the duration as you want
imgView.alpha = finalAlpha;
imgView.center = finalPoint;
}];
This "fades to invisible" self.view in 500ms.
Anything you do prior to invoking it will be set "immediately", everything you set within the `animation' block (like setting new frame coordinates) will be interpolated over the specified duration.
[UIView animateWithDuration:0.50
delay:0.0
options:( UIViewAnimationOptionAllowUserInteraction
| UIViewAnimationOptionBeginFromCurrentState
| UIViewAnimationOptionCurveEaseInOut)
animations:^ {
self.view.alpha = 0.0f ;
}
completion: ^(BOOL){
[self.view removeFromSuperview] ;
self.view = nil ;
}
] ;
You may use a CABasicAnimation object (a different one because you will animate a different KeyPath, namely alpha, for this) and use NSNumbers for fromValue and toValue.
Another (and easiest) way is to use the UIView helper methods dedicated to animation ( animateWithDuration:… and all), especially if you have to animate multiple properties at the same time (you may animate both the frame and the alpha with the same animation block if it fits your needs and simplify your code).
You can also mix CABasicAnimation for the frame and [UIView animateWithDuration:…] for the alpha, whatever combination you need depending on if your animations are complex and need to be customized (custom timing function, non-linear animation, …) or not.
//your code of changing x position will be here
now implement my code which is written below.
UIView animateWithDuration:1.0 animations:^{
imgView.alpha = 0.5;//must be in between 0 to 1
}];
now when your imageview move to next position as you are saying, write below code
//your code for imageview for next position..
now implement below code...
UIView animateWithDuration:0.5 animations:^{
imgView.alpha = 1.0;//must be in between 0 to 1
}];
let me know it is working or not!!!! Hope it helps...
Happy Coding!!!

Multiple CAAnimations On Different Views Simultaneously

I'm trying execute a couple of animations at the same time. One is transitioning from one uimageview to another and the other is animating translation.x of a label. The label resides on top of uiimageview.
But what I get is either translation working fine and transition happens immediately, or the transitioning -based on hidden property - also applies to my label which should only be shifted (it also goes from hidden to visible). I can't use caanimationgroup because they apply to different views.
//CAKeyFrameAnimation for sliding the label
...
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"transform.translation.x"];
NSArray *xValues = #[[NSNumber numberWithFloat:myLabel.bounds.origin.x],
[NSNumber numberWithFloat:myLabel.bounds.origin.x + screenHalf],
[NSNumber numberWithFloat:myLabel.bounds.origin.x + screenHalf * 4]];
[anim setValues:xValues];
NSMutableArray *timeFrames = [NSMutableArray array];
CGFloat timeStep = 1.0 / ([xValues count] - 1);
for (NSInteger i = 0; i < [xValues count]; i++)
{
[timeFrames addObject:[NSNumber numberWithFloat:timeStep * i]];
}
[anim setKeyTimes:timeFrames];
[anim setDuration:duration];
[anim setFillMode:kCAFillModeForwards];
[anim setRemovedOnCompletion:FALSE];
[myLabel.layer addAnimation:anim forKey:nil];
...
//Transitioning from uiimageview to another one
...
CATransition *transition = [CATransition animation];
[transition setDuration:duration];
[transition setType:kCATransitionFade];
//These two are uiimageviews i'm switching from and to
initial.hidden = TRUE;
next.hidden = FALSE;
//Initial and next are subviews of container which itself is a subview of viewcontroller's main view
[container.layer addAnimation:transition forKey:#""];
If I call the animations like above the transition occurs immediately and label slides across screen correctly. If I change the last line to:
[self.view.layer addAnimation:transition forKey:#""];
Then hidden animation also applies to myLabel. What is the fix for combining animations like above, and also more elaborately what is the cause?
I would wrap the entire code in a CATransaction to group the different CAAnimations into a single group.
Psuedo-code for using this with CAKeyFrameAnimation would look like:
[CATransaction begin];
// set completion block if you want
[CATransaction setCompletionBlock:^{ NSLog(#"I'm done"); }];
// start a keyframe animation
CAKeyframeAnimation *key1 = .....
// start another keyframe animation block
CAKeyframeAnimation *key2 = ......
// Maybe do a basic animation
CABasicAnimation *anim1 = .....
// close out all the animations and have them start
[CATransaction commit];

Realistic 3d rotation animation of UIView

I'm trying to find a way to animate 3D rotating of UIView. I want it looks like animation of push notification if you select "Banner" style. I'm trying to use something like
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform"];
CATransform3D t = CATransform3DIdentity;
t = CATransform3DTranslate(t, 0, -self.bounds.size.height/2, 0);
t = CATransform3DRotate(t, M_PI_2, 1, 0, 0);
t = CATransform3DTranslate(t, 0, -self.bounds.size.height/2, 0);
[anim setFromValue:[NSValue valueWithCATransform3D:CATransform3DIdentity]];
[anim setToValue:[NSValue valueWithCATransform3D:t]];
anim.removedOnCompletion = NO;
[anim setDuration:0.25];
anim.fillMode = kCAFillModeForwards;
anim.delegate = self;
[self.layer addAnimation:anim forKey:#"Rotation"];
But this looks flat and not so realistic 3D as an banners of push notifications. How can I make this animation more realistic?
You may want to take a deep look at how CMNavBarNotificationView is implemented.
Plus, there are different types of CALayer, and you might want to pick a desired one from +(Class)layerClass of your animated view. It seems me that you're sticking with plain CALayer.

CABasicAnimation Problem

So, I have read in the docs, that use of blocks like
beginAnimation
commitAnimation
is discouraged from os4.0.
So I have tried to get my code to work by using CABasicAnimation. I want to achieve, that an image's frame is resized from its thumbnail size, somewhere within my view, to a full width position e.g. (0, 120, 320, 240) - on my iPhone.
What I have so far:
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:1.0] forKey:kCATransactionAnimationDuration];
CABasicAnimation *scalingAnimation;
scalingAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
scalingAnimation.duration=1.0/2;
scalingAnimation.autoreverses=YES;
scalingAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
scalingAnimation.fromValue=[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)];
scalingAnimation.toValue=[NSValue valueWithCATransform3D:CATransform3DMakeScale(4, 4, 1)];
[b.layer addAnimation:scalingAnimation forKey:#"scaling"];
[CATransaction commit];
My nextstep would be to first try to move the image to a centered position then scale it to the correct size. However, I doubt I'm doin it the right way. Can anyone comment on my code/approach.... is there a better way?
Am pretty sure you have solved this by now, but in any case.
You shouldn't need the [CATransaction begin]; and [CATransaction commit];
The simplest way that I have found to do this kind of thing is to use CAAnimationGroup and and add the animations one by one.
An example would be
CABasicAnimation *scaleX = [CABasicAnimation animationWithKeyPath:#"transform.scale.x"];
//this is not used, as the group provides the duration
scaleX.duration = duration;
scaleX.autoreverses = NO;
scaleX.toValue = [NSNumber numberWithFloat:1.0];
scaleX.fromValue = [NSNumber numberWithFloat:scaleFrom];
scaleX.fillMode = kCAFillModeForwards;
scaleX.removedOnCompletion = NO;
CABasicAnimation *scaleY = [CABasicAnimation animationWithKeyPath:#"transform.scale.y"];
//this is not used, as the group provides the duration
scaleY.duration = duration;
scaleY.autoreverses = NO;
scaleY.toValue = [NSNumber numberWithFloat:1.0];
scaleY.fromValue = [NSNumber numberWithFloat:scaleFrom];
scaleY.fillMode = kCAFillModeForwards;
scaleY.removedOnCompletion = NO;
//add in the translation animations
NSMutableArray* animationsArray = [NSMutableArray arrayWithObjects:scaleX,
scaleY,
//and any other animations you want in the group
nil];
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = 1.0/2;
animationGroup.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseIn];
animationGroup.animations = animationsArray;
animationGroup.delegate = self;
animationGroup.removedOnCompletion = YES;
animationGroup.fillMode = kCAFillModeForwards;
[animationGroup setValue:#"imageTransform" forKey:#"AnimationName"];
[b.layer addAnimation:animationGroup forKey:#"imageTransform"];
There are a few gotchas though. Animations are purely visual, so before running the animations, set your eventual view frame.
You will notice that the scales end at 1, this is to ensure that you dont end up with a scaled image layer. Instead we start it scaled and bring it back to normal.
Translations should be done in the same way.
Hope this helps
//in Event Method Copy Below code to place the image to the center
CABasicAnimation* positionAnimation;
positionAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
positionAnimation.fromValue = [NSValue valueWithCGPoint:imageView.layer.position];
positionAnimation.toValue = [NSValue valueWithCGPoint:centerPointofView];
positionAnimation.duration = 2.0;
positionAnimation.fillMode = kCAFillModeForwards;
positionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
positionAnimation.removedOnCompletion = NO;
positionAnimation.delegate = self;
[imageView.layer addAnimation:positionAnimation forKey:#"positionAnimation"];
// For Scale After image is in center copy below code
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag{
CAAnimation *animation = [imageView.layer animationForKey:#"positionAnimation"];
if (animation == theAnimation)
{
CABasicAnimation* scaleAnimation;
scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scaleAnimation.fromValue = [NSNumber numberWithFloat:1];
scaleAnimation.toValue = [NSNumber numberWithFloat:4.0];
scaleAnimation.duration = 2.2;
scaleAnimation.fillMode = kCAFillModeForwards;
scaleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
scaleAnimation.removedOnCompletion = NO;
scaleAnimation.delegate = self;
[imageView.layer addAnimation:scaleAnimation forKey:#"scaleAnimation"];
}
else
{
//if you want changes to image should remain forever
//place your code for scale & transform here
....................
//now simple remove animation from layer
[imageView.layer removeAllAnimations];
}
}
Cant you use
[UIView animateWithDuration:2.0
animations:^{
//animations
}
completion:^(BOOL finished){
// completion methods
}];

CATransaction (still) Not Animating

I have a problem very similar to this one :
CATransaction Not Animating
I'm just trying to animate a view layer using CATransaction. My problem is that the transform is applied to the view immediatly.
I tried to perform the animation using performSelector:withObject:afterDelay: without success.
Here is my code :
- (void)viewDidLoad {
[super viewDidLoad];
view = [[[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)] autorelease];
view.backgroundColor = [UIColor blackColor];
[self.view addSubview:view];
[self performSelector:#selector(animateView) withObject:nil afterDelay:0.1];
}
-(void) animateView {
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:3.0f] forKey:kCATransactionAnimationDuration];
CALayer *layer = view.layer;
layer.position = CGPointMake(20,
300);
CATransform3D transform = CATransform3DMakeScale(2.0f, 2.0f, 1.0f);
transform = CATransform3DRotate(transform, acos(-1.0f)*1.5f, 1.5f, 1.5f, 1.5f);
layer.transform = transform;
[CATransaction commit];
}
Does anybody knows what is going wrong ?
Thanks, Vincent.
when animating a view's backing layer, you need to be inside a UIView animation block, not just a CATransaction:
[UIView beginAnimations:nil context:NULL];
// ... your animation code
[UIView commitAnimations];
or you could use CAAnimationGroup and group the scale and rotation animations like this..
CAAnimationGroup *aniGroup = [CAAnimationGroup animation];
// setup aniGroup properties
CABasicAnimation *scaleAnimation = [CABasicAnimation defaultValueForKey:#"transform.scale"];
scaleAnimation.fromValue = [NSNumber numberWithFloat:0.0];
scaleAnimation.toValue = [NSNumber numberWithFloat:1.0];
// setup scaleAnimation properties
CABasicAnimation *rotateAnimation = [CABasicAnimation defaultValueForKey:#"transform.rotate"];
// setup rotateAnimation properties
// ....
aniGroup.animations = [NSArray arrayWithObjects:rotateAnimation, scaleAnimation, nil];
[self.layer layoutIfNeeded];
[self.layer addAnimation:aniGroup forKey:#"myAnimation"];
Something like that.... Might not be exactly copy, paste, and build-able. ;)
Hope this helps!