iOS Recipes - Matt Drance [56]
To keep the artwork simple, we will build the classic line-drawn seagull animation, with the two wings seeming to flap up and down as the body bounces between them (see Figure 32, Frames of the seagull animation ). To render the wings of our bird, we just need a pair of quadratic bezier curves.
The PRPBird class extends the UIImageView class to build and assign our array of images.
GraphicsGarden/PRPBird.m
- (void)didMoveToSuperview {
if (!self.animationImages) {
self.animationImages = [self arrayOfFrames];
}
}
Figure 32. Frames of the seagull animation
* * *
To ensure that the UIImageView has been added to the main view before we build our images, we override the didMoveToSuperview method to assign the array to the animationImages property.
GraphicsGarden/PRPBird.m
- (NSArray *)arrayOfFrames {
NSMutableArray *imageArray = [NSMutableArray arrayWithCapacity:COUNT];
for (CGFloat i = LOWWING; i < HIGHWING; i+=STEPS) {
[imageArray addObject:[self animationFrame:i]];
}
for (CGFloat i = HIGHWING; i > LOWWING; i-=STEPS) {
[imageArray addObject:[self animationFrame:i]];
}
return [NSArray arrayWithArray:imageArray];
}
The arrayOfFrames method builds up the NSMutableArray of images by looping through two sets of calls to the animationFrame, where the wings are drawn. The parameter i relates to the height of the edge of each wing. We use two separate loops here because we need to generate all of the frames for our animation—both the wings flapping downward and the wings flapping back up again. Because the animation automatically repeats, it will give the impression of continuous movement.
GraphicsGarden/PRPBird.m
- (UIImage *)animationFrame:(CGFloat)frameNum {
CGFloat width = self.bounds.size.width;
CGFloat height = self.bounds.size.height;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height),
NO, 0);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, frameNum)];
[path addQuadCurveToPoint:CGPointMake(0.5, 0.6-frameNum/3)
controlPoint:CGPointMake(0.25, 0.25)];
[path addQuadCurveToPoint:CGPointMake(1, frameNum)
controlPoint:CGPointMake(0.75, 0.25)];
[path applyTransform:CGAffineTransformMakeScale(width, height)];
path.lineWidth = height/30;
[path stroke];
UIImage *frameImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return frameImage;
}
With our recipes so far, we’ve always placed drawing code in the drawRect: method, but here we’re actually building a series of fixed images, so we need to use a different technique. We will create our own graphics context by calling the Core Graphics function, UIGraphicsBeginImageContextWithOptions. UIBezierPath uses the default context, in this case the one we just created. The two curves are added to the path, varying the start points and end points a little for each frame. Using the UIGraphicsGetImageFromCurrentImageContext method, a UIImage is built from our context and passed back to be added to the array.
GraphicsGarden/MainViewController.m
CGRect rect = CGRectMake(-width/5, width/8, width/8, height/8);
PRPBird *bird = [[PRPBird alloc] initWithFrame:rect];
[self.view addSubview:bird];
[bird release];
bird.animationDuration = 1.0;
[bird startAnimating];
CABasicAnimation *birdAnim = [CABasicAnimation animation];
birdAnim.keyPath = @"position";
CGPoint birdPos = CGPointMake(width, bird.center.y*2);
birdAnim.toValue = [NSValue valueWithCGPoint:birdPos];
birdAnim.duration = DURATION/2;
birdAnim.repeatCount = MAXFLOAT;
[bird.layer addAnimation:birdAnim forKey:@"pos"];
Using the PRPBird class is no different from using a regular UIImageView, except that the animation