CABasicAnimation is removed in a UITableView - iphone

I have a UITable view which populates some cells.
Few of the cells have a spinning image. The image is spinned using an animation:
[image.layer removeAllAnimations];
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.duration = 1.0;
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 * 1.0 * rotationAnimation.duration ];
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = HUGE_VALF;
[image.layer addAnimation:rotationAnimation forKey:nil];
For some reason, not all images have the animation. It almost seems like when I start scrolling the table, some of the animations are removed/disabled.
For some reason

Change your last line to use a random key:
[image.layer addAnimation:rotationAnimation forKey:[NSString stringWithFormat:#"%d", arc4random()]];
This fixed it in my case.

Related

Flipping two UIView using CATransform3D

I have created Visiting Card which has Front view & back View. When i tap on the front view it should flip the UIView & show the back side of the View.
Here is Code which I am trying:
CATransform3D transform = CATransform3DMakeRotation(M_PI, 0, 1, 0);
transform.m34 = 1.0/700.0;
CABasicAnimation *rotation = [CABasicAnimation animationWithKeyPath:#"transform"];
rotation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
rotation.toValue = [NSValue valueWithCATransform3D:transform];
rotation.duration = DURATION;
CABasicAnimation *translation = [CABasicAnimation animationWithKeyPath:#"position"];
translation.fromValue = [NSValue valueWithCGPoint:CGPointMake(self.imageview.center.x,[[self.imageview superview] center].y-45)];
translation.toValue = [NSValue valueWithCGPoint:CGPointMake(_frame.origin.x+_frame.size.width/2,
_frame.origin.y+_frame.size.height/2)];
translation.duration = DURATION;
CABasicAnimation *translation = [CABasicAnimation animationWithKeyPath:#"position"];
translation.fromValue = [NSValue valueWithCGPoint:CGPointMake(self.imageview.center.x,[[self.imageview superview] center].y-45)];
translation.toValue = [NSValue valueWithCGPoint:CGPointMake(_frame.origin.x+_frame.size.width/2,
_frame.origin.y+_frame.size.height/2)];
translation.duration = DURATION;
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = #[ translation, rotation ];
group.duration = DURATION;
group.delegate = self;
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
[layer addAnimation:group forKey:nil];
Above code works perfectly for single view. How to combine the 2nd view to get flipping from front & back effect.
Using Core Animation
You can add both front and back to a container view. The back view will have a 180 degree rotation around Y and the front will be just normal. Both layers will be single sided (by setting layer.doubleSided = NO;.
Then when you apply the rotation you would animate the rotation of the container view so that both front and back animate at the same time.
UIView transitions
Or you could just use the built in flip animation
transitionFromView:toView:duration:options:completion:
and pass either UIViewAnimationOptionTransitionFlipFromLeft or UIViewAnimationOptionTransitionFlipFromRight for the option.

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!

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
}];

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.

Moving Image 360 Degree

How can we Move an Image 360 Degree from the starting point to the ending point?
Moving Image 360 Degree?
You can rotate a view, by some number of radians, regardless of whether it is less than a full rotation or many multiples of a full rotation, without having to split the rotation into pieces. As an example, the following code will spin a view, once per second, for a specified number of seconds. You can easily modify it to spin a view by a certain number of rotations, or by some number of radians.
- (void) runSpinAnimationWithDuration:(CGFloat) duration;
{
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 /* full rotation*/ * rotations * duration ];
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = 1.0;
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[myView.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
A 360 degree rotation in any axis leaves the view unchanged. So don't touch the image, and you're good to go!
You could use an animation function (CAValueFunction) for this too:
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
rotationAnimation.duration = duration;
rotationAnimation.fromValue = [NSNumber numberWithFloat:0.0];
rotationAnimation.toValue = [NSNumber numberWithFloat:M_PI * 2.0];
rotationAnimation.valueFunction = [CAValueFunction functionWithName:kCAValueFunctionRotateZ];
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[myView.layer addAnimation:rotationAnimation forKey:nil];