views:

942

answers:

2

I'm trying to do some animations using Core Animation on the iphone. I'm using CABasicAnimation on CALayer. It's a straight forward animation from a random place at the top of the screen to the bottom of the screen at random speed, I have 30 elements that doing the same animation continuously until another action happens. But the performance on the iPhone 3G is very sluggish when the animations start. The image is only 8k.

Is this the right approach? How should I change so it performs better.

// image cached somewhere else.
CGImageRef imageRef = [[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:name ofType:@"png"]] CGImage];

- (void)animate:(NSTimer *)timer
{
 int startX   = round(radom() % 320);
 float speed   = 1 / round(random() % 100 + 2);

 CALayer *layer  = [CALayer layer];
 layer.name   = @"layer";
 layer.contents  = imageRef; // cached image
 layer.frame   = CGRectMake(0, 0, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));

 int width   = layer.frame.size.width;
 int height   = layer.frame.size.height;

 layer.frame   = CGRectMake(startX, self.view.frame.origin.y, width, height);

 [effectLayer addSublayer:layer];

 CGPoint start  = CGPointMake(startX, 0);
 CGPoint end   = CGPointMake(startX, self.view.frame.size.height);
 float repeatCount = 1e100;

 CABasicAnimation *animation  = [CABasicAnimation animationWithKeyPath:@"position"];
 animation.delegate    = self;
 animation.fromValue    = [NSValue valueWithCGPoint:start];
 animation.toValue    = [NSValue valueWithCGPoint:end];
 animation.duration    = speed;
 animation.repeatCount   = repeatCount;
 animation.autoreverses   = NO;
 animation.removedOnCompletion = YES;
 animation.fillMode    = kCAFillModeForwards;

 [layer addAnimation:animation forKey:@"position"];
}

The animations are fired off using a NSTimer.

animationTimer = [NSTimer timerWithTimeInterval:0.2 target:self selector:@selector(animate:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSDefaultRunLoopMode];
+2  A: 

Okay, a couple of things here:

Why are you using an NSTimer to repeatedly fire the animation? Just have the animations repeat. If they need pause and resync you can do that with a keyed animation. By programming it all up you can let the renderer run without having to frequently synch in new information into the render tree.

You are doing:

CGImageRef imageRef     = [[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:name ofType:@"png"]] CGImage];

for 30 objects each on a timer that fires every 0.2 seconds? Have you profiled your app? What percentage of the time is spent in image decoding? If you switch over to using imageNamed: iPhone OS will cache images so long as there is enough ram, which should reduce your decode time:

CGImageRef imageRef = [[UIImage imageNamed:[NSString stringWithFormat:@"%@.png", name]] CGImage];

Also, do your images have transparency that needs to be alpha composited? If you can avoid alpha it is a huge speed win on the older iPhones.

Louis Gerbarg
I'm using NSTimer to create 30 different animations with a 0.2 interval. By different, I mean different position and speed.Actually I have already cached the imageRef before calling animate:. I cut/paste the code in there, but the cached image helped some, but the animation is still really sluggish. You are using imageNamed, from what I could understand is using imageWithContentsOfFile would be better, right? The image does contain transparency. The image is a circular shaped, so no way around that.
nico
I was considering about going with keyframed animation instead, would that improve performance?I do need to pause the animation and start again. How do I go about implementing it in keyframe animation with different start and end positions, and just let the renderer do the work?
nico
Well, if you are just kicking of 30 different animations then timer seems like the way to go. As for the caching thing, it is good to hear you already doing it, but it probably makes it harder to give advice when you have substantive differences between what you post and what you are doing. Given what you have said I doubt key framing will help that much. One point, there is a big difference between complete transparency and alpha composited transparent from a performance standpoint. Given what you have posted I don't know if there is much more anyone can say without actual traces.
Louis Gerbarg
Ah, right. The image does have alpha transparency. I will try to make some new ones and try them out. The code I have is bit different becuase I have put creating the CALayer and CABasicAnimation in a seperate method, but it basicly is the same as the code I pasted here. I will try to do them as keyframe animations .
nico
A: 

In my experience layer.contents = imageRef; // cached image is the most expensive call in this method.

For testing purposes I would use the same image object on them all. The first might be sluggish but the rest will be smooth.

As far as I understand, the reason for this is because an UIImage object even when created does actually pull all the data from the file until it is going to be drawn on screen.

There are several posts out there that show that using nsurlconnections to download the images with the file:// protocol will actually thread the file getting (without writing any threading code) and because you are animated layers and not uiviews, it really will be asynchronous.

maxpower