iOS: jerkiness when animation stops and keeping animation in sync with modal - ios

I am trying to perform a transform for the views on y axis using CABasicAnimation:
for(int x = 0; x < viewsArray.count; x++)
{
CABasicAnimation *startAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
[startAnimation setToValue:[NSNumber numberWithFloat:DEGREES_RADIANS(-55.0f)]];
[startAnimation setDuration:5.0];
startAnimation.delegate = self;
UIView *view = [viewsArray objectAtIndex:x];
[startAnimation setValue:#"rotate" forKey:#"id"];
[view.layer addAnimation:startAnimation forKey:#"rotate"];
}
Nothing fancy. I would like just to keep the modal and the animation in sync, so in
animationDidStop I try to set the transform again using a for-loop:
if([[anim valueForKey:#"id"] isEqualToString:#"rotate"])
{
aTransform = CATransform3DRotate(aTransform, DEGREES_RADIANS(-55.0), 0.0, 1.0, 0.0);
for(int x = 0; x < viewsArray.count; x++)
{
UIView *view = [viewsArray objectAtIndex:x];
view.layer.transform = aTransform;
}
}
But I realized after the animation stops the animation would jerk to the angle set by the transform in animationDidStop.
Does any one know why and what would be the best way to do this without having to use removedOnCompletion = NO;? I would like to avoid using that and would like to keep the animation always in sync with the modal layer.

You can set it at the same time as you add your animation, but you'll need to supply a fromValue to the animation to stop it updating the presentation layer immediately:
for(int x = 0; x < viewsArray.count; x++)
{
UIView *view = [viewsArray objectAtIndex:x];
NSString *keyPath = #"transform.rotation.y";
NSNumber *toValue = [NSNumber numberWithFloat:DEGREES_RADIANS(-55.0f)];
CABasicAnimation *startAnimation = [CABasicAnimation animationWithKeyPath:keyPath];
[startAnimation setFromValue:[view.layer valueForKeyPath:keyPath]];
//[startAnimation setToValue:toValue]; We don't need to set this as we're updating the current value on the layer instead.
[startAnimation setDuration:5.0];
[view.layer addAnimation:startAnimation forKey:#"rotate"];
[view.layer setValue:toValue forKeyPath:keyPath]; // Update the modal
}

Related

CABasicAnimation only works with layers created in the same method

I need to animate different CATextLayers depending on some conditions. This is why I wrote a method that accepts the layer and animates it. Here it is:
- (void) animateText:(CATextLayer*) text withColor: (CGColorRef) color andDuration: (NSTimeInterval) duration
{
CABasicAnimation *colorAnimation = [CABasicAnimation
animationWithKeyPath:#"foregroundColor"];
colorAnimation.duration = duration;
colorAnimation.fillMode = kCAFillModeForwards;
colorAnimation.removedOnCompletion = NO;
colorAnimation.fromValue = (id)[UIColor redColor].CGColor;
colorAnimation.toValue = (id)[UIColor blackColor].CGColor;
colorAnimation.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionLinear];
[text addAnimation:colorAnimation
forKey:#"colorAnimation"];
"text" is a CATextLayer created, initialized and added to sublayers in some other method.
The problem is this code does not make the "text" layer animate.
(Please, don't pay attention that the method receives the color. I have temporarily put red and black for testing.)
I started testing. If I create a layer within this method and then try to animate this newly created method, it works ok.
If I copy-paste the code to the method where all layers are created and try to animate one of them - it works fine too.
But I need to animate different layers at different moments, so I see this function as essential.
Please, explain me what I am doing wrong.
Thank you in advance.
UPDATED:
Here's how the layers are created and placed onto UIView
//creating 110 text layers
CGFloat leftPoint = 0;
CGFloat topPoint = 0;
for (int i=0; i<11; ++i)
{
for (int j=0; j<10; ++j)
{
CATextLayer *label = [[CATextLayer alloc] init];
[label setString:[_labels objectAtIndex:j+(i*10)]]; // _labels contains letters to display
[label setFrame:CGRectMake(leftPoint, topPoint, _labelWidth, _labelHeight)]; // _labelWidth and _labelHeight are valid numbers
[label setAlignmentMode:kCAAlignmentCenter];
[label setForegroundColor:_textColor]; // _textColor is a CGColorRef type
[label setFont:(__bridge CFTypeRef)(_font.fontName)]; // font is UIFont type
[label setFontSize:[_font pointSize]];
[_textViews addObject:label]; // saving to internal array variable
[[self layer] addSublayer:label];
leftPoint += _labelWidth;
}
leftPoint = 0;
topPoint += _labelHeight;
}
If I place a code like this in the same method where I do animating, it works.
And it stops working when I pass the layer in the method like this:
for (int i = 0; i<labelsToHighlight.count; ++i) // labelsToHighlight is an array containing indexes of the CATextLayers which I need to animate
{
NSUInteger index = [[labelsToHighlight objectAtIndex:i] intValue];
[self animateText:[_textViews objectAtIndex:index] withColor: _highlightedTextColor andDuration:_redrawAnimationDuration];
}
It seems I managed to make it work:
Here's the correct method:
- (void) animateText:(CATextLayer*) text fromColor: (CGColorRef) fromColor toColor: (CGColorRef) toColor andDuration: (NSTimeInterval) duration
{
CABasicAnimation *colorAnimation = [CABasicAnimation
animationWithKeyPath:#"foregroundColor"];
colorAnimation.duration = duration;
colorAnimation.fillMode = kCAFillModeForwards;
colorAnimation.removedOnCompletion = NO;
colorAnimation.fromValue = (__bridge id)fromColor;
colorAnimation.toValue = (__bridge id) toColor;
colorAnimation.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionLinear];
[text setForegroundColor:toColor]; // This is what I have actually added
[text addAnimation:colorAnimation forKey:#"colorAnimation"];
}
But to be fully honest, I don't understand why that line did make it work.
I suppose that without resetting foreground color the animation should have blinked and foreground color would return to its initial value. In reality though absolutely nothing happened on the screen.
After adding this everything works just fine.

moving a view multiple times

I got an array "coordList" with arrays holding x-y-coordinates.
I want to move a view along these coordinates.
If you have a better way to do this please tell me how to do it.
The way I do it has a big problem. It jumps directley to the last animation and I know why but not how to fix it.
My code:
count = 1;
for(NSArray *array in coordList) {
[UIView animteWithDuration:1 animations:^(void){
CGRect r = [[self.subviews lastObject] frame];
r.origin.x = 103*[coordList[count][0]integerValue];
r.origin.y = 103*[coordList[count][1]integerValue];
[[self.subviews lastObject] setFrame:r];
count++;
[UIView commitAnimations];
}
}
Sorry for my bad english :)
What is happening is that you are committing multiple animations regarding the same view, so each time you commit a new animation for that view it overrides the other existing animations.
You need to set up your animations to trigger one after the other. So a simple for loop won't work that well.
I'd recommend a recursive routine like this:
//set a property to keep current count
#property (nonatomic, asssign) NSInteger currentFrame;
//initialize it to 0 on init
currentFrame = 0;
//call this once when ready to animate
[self animationWithCurrentFrame];
//it will call this routine once for each array.
- (void)animationWithCurrentFrame {
__block NSArray *array = [coordList objectAtIndex:currentFrame];
[UIView animateWithDuration:2.0
animations:^{
CGRect r = [[self.subviews lastObject] frame];
r.origin.x = 103*[array[0]integerValue];
r.origin.y = 103*[array[1]integerValue];
[[self.subviews lastObject] setFrame:r];
}
completion:^(BOOL finished){
currentFrame++;
if (currentFrame < [coordList count]) {
[self animationWithCurrentFrame];
}
}];
}
This is an ideal application of a CAKeyFrameAnimation. And you do not do a bunch of animations, but rather you define a path, and then perform a single animation, specifying that path as the "position":
UIView *viewToAnimate = [self.subviews lastObject];
// create UIBezierPath for your `coordList` array
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(103*[coordList[0][0]integerValue], 103*[coordList[0][1]integerValue])];
for (NSInteger i = 1; i < [coordList count]; i++)
{
[path moveToPoint:CGPointMake(103*[coordList[i][0]integerValue], 103*[coordList[i][1]integerValue])];
}
// now create animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.path = [path CGPath];
animation.duration = 2.0;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[viewToAnimate.layer addAnimation:animation forKey:#"position"];
For this to work, you have to add the QuartzCore framework to your project and import the appropriate header at the top of your .m file:
#import <QuartzCore/QuartzCore.h>
For more information, see Keyframe Animation to Change Layer Properties in the Core Animation Programming Guide.
If you're using auto layout, don't forget to reset your constraints for this view when you're done.

Custom CAGradientLayer doesn't animate

I've made a custom class of CAGradientLayer (added radial gradient option).
I works but I can't figure out why I can't animate the colors.
Here's my code.
The animation is a simple CABasicAnimation where I set the toValue to a new array with colors.
#import "ExtCAGradientLayer.h"
NSString * const kExt_CAGradientLayerRadial = #"RadialGradient";
#implementation ExtCAGradientLayer
#synthesize gradientDrawingOptions;
+(BOOL)needsDisplayForKey:(NSString *)key
{
return [key isEqualToString:#"type"] || [super needsDisplayForKey:key];
}
- (void)drawInContext:(CGContextRef)theContext
{
if ([self.type isEqualToString:kExt_CAGradientLayerRadial]) {
size_t num_locations = self.locations.count;
int numbOfComponents = 0;
CGColorSpaceRef colorSpace = NULL;
if (self.colors.count) {
CGColorRef colorRef = (__bridge CGColorRef)[self.colors objectAtIndex:0];
numbOfComponents = CGColorGetNumberOfComponents(colorRef);
colorSpace = CGColorGetColorSpace(colorRef);
}
float *locations = calloc(num_locations, sizeof(float));
float *components = calloc(num_locations, numbOfComponents * sizeof(float));
for (int x = 0; x < num_locations; x++) {
locations[x] = [[self.locations objectAtIndex:x] floatValue];
const CGFloat *comps = CGColorGetComponents((__bridge CGColorRef)[self.colors objectAtIndex:x]);
for (int y = 0; y < numbOfComponents; y++) {
int shift = numbOfComponents * x;
components[shift + y] = comps[y];
}
}
CGPoint position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGFloat radius = floorf(MIN(self.bounds.size.width, self.bounds.size.height) / 2);
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, num_locations);
CGContextDrawRadialGradient(theContext, gradient, position, 0, position, radius, self.gradientDrawingOptions);
free(locations);
free(components);
}
Layers have a bunch of implicit animations tied to them. This can make them a little funny when interacting with CABasicAnimation. My first recommendation is to avoid CABasicAnimation when working with layers. There's almost never a good reason for it, since layers know how to animate themselves. It's much easier just to do this:
[CATransaction begin];
[CATransaction setAnimationDuration:3];
layer.colors = [NSArray arrayWithObjects:
(__bridge id)[UIColor redColor].CGColor,
[UIColor greenColor].CGColor,
nil];
[CATransaction commit];
That's a 3-second animation of the colors. Easy. The begin/commit aren't technically needed in most cases, but they limit the scope of the setAnimationDuration: in case you're animating other things in this transaction.
But maybe you like CABasicAnimation, or you're using it something that returns them (I do that sometimes). You can still combine them with CALayer, but you have to tell CALayer to stop its automatic animation (setDisableActions:). And you still want to use the layer setters rather than toValue:.
[CATransaction begin];
[CATransaction setDisableActions:YES];
CABasicAnimation *anim = [[CABasicAnimation alloc] init];
anim.duration = 3;
[layer addAnimation:anim forKey:#"colors"];
layer.colors = [NSArray arrayWithObjects:
(__bridge id)[UIColor redColor].CGColor,
[UIColor greenColor].CGColor,
nil];
[CATransaction commit];
If you use toValue: rather than the setter, it will work, but will "jump back" to the original value at the end.
Avoid solutions that suggest setting fillMode and removedOnCompletion. These generate many subtle problems if you don't understand exactly what they're doing. They seldom are what you really mean; they just happen to work as long as things are very simple.

iOS CAKeyFrameAnimation Scaling Flickers at animation end

In another test of Key Frame animation I am combining moving a UIImageView (called theImage) along a bezier path and scaling larger it as it moves, resulting in a 2x larger image at the end of the path. My initial code to do this has these elements in it to kick off the animation:
UIImageView* theImage = ....
float scaleFactor = 2.0;
....
theImage.center = destination;
theImage.transform = CGAffineTransformMakeScale(1.0,1.0);
CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:#"bounds.size"];
[resizeAnimation setToValue:[NSValue valueWithCGSize:CGSizeMake(theImage.image.size.height*scaleFactor, theImage.image.size.width*scaleFactor)]];
resizeAnimation.fillMode = kCAFillModeBackwards;
resizeAnimation.removedOnCompletion = NO;
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.path = [jdPath path].CGPath;
pathAnimation.fillMode = kCAFillModeBackwards;
pathAnimation.removedOnCompletion = NO;
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:pathAnimation, resizeAnimation, nil];
group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
group.removedOnCompletion = NO;
group.duration = duration;
group.delegate = self;
[theImage.layer addAnimation:group forKey:#"animateImage"];
Then, when the animation completes I want to retain the image at the larger size, so I implement:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
theImage.transform = CGAffineTransformMakeScale(scaleFactor,scaleFactor);
}
This all works .. sort of. The problem is that at the end of the animation theImage flickers for a brief moment - just enough to make it look bad. I am guessing that this is the transition at the end of the animation where I set the transform to the new size.
In experimenting with this I tried a slightly different form of the above, but still got the same flicker:
CAKeyframeAnimation *resizeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
NSValue* startSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, 1.0, 1.0, 1.0)];
NSValue* endSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, scaleFactor, scaleFactor, 1.0)];
NSArray* sizeKeys = [NSArray arrayWithObjects:startSizeKey, endSizeKey, nil];
[resizeAnimation setValues:sizeKeys];
....
theImage.transform = CGAffineTransformMakeScale(scaleFactor,scaleFactor);
But when I ended the animation at the same size as the original, there was NO flicker:
CAKeyframeAnimation *resizeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
NSValue* startSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, 1.0, 1.0, 1.0)];
NSValue* middleSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, scaleFactor, scaleFactor, 1.0)];
NSValue* endSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, 1.0, 1.0, 1.0)];
NSArray* sizeKeys = [NSArray arrayWithObjects:startSizeKey, middleSizeKey, endSizeKey, nil];
[resizeAnimation setValues:sizeKeys];
....
theImage.transform = CGAffineTransformMakeScale(1.0,1.0);
So my big question is how can I animate this image without the flicker, and end up with a different size at the end of the animation?
Edit March 2nd
My initial tests were with scaling the image up. I just tried scaling it down (IE scaleFactor = 0.4) and the flickering was a lot more visible, and a lot more obvious as to what I am seeing. This was the sequence of events:
Original sized image is painted on the screen at the starting location.
As the image moves along the path it shrinks smoothly.
The fully shrunk image arrives at the end of the path.
The image is then painted at its original size.
The image is finally painted at its shrunken size.
So it seems to be step 4 that is the flickering that I am seeing.
Edit March 22
I have just uploaded to GitHub a demo project that shows off the moving of an object along a bezier path. The code can be found at PathMove
I also wrote about it in my blog at Moving objects along a bezier path in iOS
It can be tricky to animate a view's layer using Core Animation. There are several things that make it confusing:
Setting an animation on a layer doesn't change the layer's properties. Instead, it changes the properties of a “presentation layer” that replaces the original “model layer” on the screen as long as the animation is applied.
Changing a layer's property normally adds an implicit animation to the layer, with the property name as the animation's key. So if you want to explicitly animate a property, you usually want to set the property to its final value, then add an animation whose key is the property name, to override the implicit animation.
A view normally disables implicit animations on its layer. It also mucks around with its layer's properties in other somewhat mysterious ways.
Also, it's confusing that you animate the view's bounds to scale it up, but then switch to a scale transformation at the end.
I think the easiest way to do what you want is to use the UIView animation methods as much as possible, and only bring in Core Animation for the keyframe animation. You can add the keyframe animation to the view's layer after you've let UIView add its own animation, and your keyframe animation will override the animation added by UIView.
This worked for me:
- (IBAction)animate:(id)sender {
UIImageView* theImage = self.imageView;
CGFloat scaleFactor = 2;
NSTimeInterval duration = 1;
UIBezierPath *path = [self animationPathFromStartingPoint:theImage.center];
CGPoint destination = [path currentPoint];
[UIView animateWithDuration:duration animations:^{
// UIView will add animations for both of these changes.
theImage.transform = CGAffineTransformMakeScale(scaleFactor, scaleFactor);
theImage.center = destination;
// Prepare my own keypath animation for the layer position.
// The layer position is the same as the view center.
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
positionAnimation.path = path.CGPath;
// Copy properties from UIView's animation.
CAAnimation *autoAnimation = [theImage.layer animationForKey:#"position"];
positionAnimation.duration = autoAnimation.duration;
positionAnimation.fillMode = autoAnimation.fillMode;
// Replace UIView's animation with my animation.
[theImage.layer addAnimation:positionAnimation forKey:positionAnimation.keyPath];
}];
}
CAAnimations will flicker at the end if the terminal state was assigned in such a way that it itself created an implicit animation. Keep in mind CAAnimations are temporary adjustments of an object properties for the purposes of visualizing transition. When the animation done, if the layer's state is still the original starting state, that is what is going to be displayed ever so temporarily until you set the final layer state, which you do in your animationDidStop: method.
Furthermore, your animation is adjusting the bounds.size property of your layer, so you should similarly set your final state rather than using the transform adjustment as your final state. You could also use the transform property as the animating property in the animation instead of bounds.size.
To remedy this, immediately after assigning the animation, change the layer's permeant state to your desired terminal state so that when the animation completes there will be no flicker, but do so in such a manner to no trigger an implicit animation before the animation begins. Specifically, in your case you should do this at the end of your animation set up:
UIImageView* theImage = ....
float scaleFactor = 2.0;
....
theImage.center = destination;
theImage.transform = CGAffineTransformMakeScale(1.0,1.0);
CGSize finalSize = CGSizeMake(theImage.image.size.height*scaleFactor, theImage.image.size.width*scaleFactor);
CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:#"bounds.size"];
[resizeAnimation setToValue:[NSValue valueWithCGSize:finalSize]];
resizeAnimation.fillMode = kCAFillModeBackwards;
resizeAnimation.removedOnCompletion = NO;
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.path = [jdPath path].CGPath;
pathAnimation.fillMode = kCAFillModeBackwards;
pathAnimation.removedOnCompletion = NO;
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:pathAnimation, resizeAnimation, nil];
group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
group.removedOnCompletion = NO;
group.duration = duration;
group.delegate = self;
[theImage.layer addAnimation:group forKey:#"animateImage"];
[CATransaction begin];
[CATransaction setDisableActions:YES];
theImage.bounds = CGRectMake( theImage.bounds.origin.x, theImage.bounds.origin.y, finalSize.width, finalSize.height );
[CATransaction commit];
and then remove the transform adjustment in your animationDidStop: method.
I was experimenting with some CAAnimations this week and was noticing that there was a flickering at the end of my animations. In particular, I would animation from a circle to a square, while changing the fillColor as well.
Each CAAnimation has a property called removedOnCompletion which defaults to YES. This means that the animation will disappear (i.e. transitions, scales, rotations, etc.) when the animation completes and you'll be left with the original layer.
Since you already have set your removedOnCompletion properties to NO, I would suggest trying to shift your execution of your animations to use CATransactions, instead of delegates and animationDidStop...
[CATransaction begin];
[CATransaction setDisableActions:YES];
[CATransaction setCompletionBlock: ^{ theImage.transform = ...}];
// ... CAAnimation Stuff ... //
[CATransaction commit];
You put the transaction's completion block call before you create your animations, as per:
http://zearfoss.wordpress.com/2011/02/24/core-animation-catransaction-protip/
The following is from one of my methods:
[CATransaction begin];
CABasicAnimation *animation = ...;
animation.fromValue = ...;
animation.toValue = ...;
[CATransaction setCompletionBlock:^ { self.shadowRadius = _shadowRadius; }];
[self addAnimation:animation forKey:#"animateShadowOpacity"];
[CATransaction commit];
And, I constructed this animation and it works fine for me with no glitches at the end:
The setup and trigger are custom methods I have in a window, and i trigger the animation on mousedown.
UIImageView *imgView;
UIBezierPath *animationPath;
-(void)setup {
canvas = (C4View *)self.view;
imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"img256.png"]];
imgView.frame = CGRectMake(0, 0, 128, 128);
imgView.center = CGPointMake(384, 128);
[canvas addSubview:imgView];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[UIImageView animateWithDuration:2.0f animations:^{
[CATransaction begin];
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.duration = 2.0f;
pathAnimation.calculationMode = kCAAnimationPaced;
animationPath = [UIBezierPath bezierPath];
[animationPath moveToPoint:imgView.center];
[animationPath addLineToPoint:CGPointMake(128, 512)];
[animationPath addLineToPoint:CGPointMake(384, 896)];
pathAnimation.path = animationPath.CGPath;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
[imgView.layer addAnimation:pathAnimation forKey:#"animatePosition"];
[CATransaction commit];
CGFloat scaleFactor = 2.0f;
CGRect newFrame = imgView.frame;
newFrame.size.width *= scaleFactor;
newFrame.size.height *= scaleFactor;
newFrame.origin = CGPointMake(256, 0);
imgView.frame = newFrame;
imgView.transform = CGAffineTransformRotate(imgView.transform,90.0*M_PI/180);
}];
}

Moving Animation of a CALayer?

I have a array of CALayers that i'm looping and trying to move.
Tile is a CALayer subclass and has a CGRect property named originalFrame where i store the frame i want to animate to.
When i'm useing the code below everything is instant moved to the correct possition and there is no 4 sec animation. How can i animate these CALayer?
for (int i = 0; i < [tileArray count]; i++) {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDelay:i];
[UIView setAnimationDuration:4];
Tile *currentCard = (Tile*)[tileArray objectAtIndex:i];
currentCard.frame = currentCard.originalFrame;
[UIView commitAnimations];
}
You have two problems: the first is that you're trying to animate the layer's frame directly. Since this is a derived property, you can't do that. Instead, you have to animate the position property. http://developer.apple.com/library/mac/#qa/qa1620/_index.html
Second, you're using UIView's +beginAnimations API, but you say your Tile objects are CALayers, not UIViews. So you don't need to use +beginAnimations. Instead you need to use a CAAnimation object, like CABasicAnimation (untested):
for (Tile *tile in tileArray)
{
static NSString * const kProperty = #"position";
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:kProperty];
animation.duration = 4.0f;
animation.fromValue = [tile valueForKey:kProperty];
animation.toValue = [NSValue valueWithCGRect:tile.originalFrame];
[tile addAnimation:animation forKey:kProperty];
}

Resources