iOS Recipes - Matt Drance [55]
Solution
You can define a CALayer to display a shadow that follows its nontransparent shape, offset from the image contents, to give the effect of the view being shown in relief, set apart from the background. Apple introduced a new property in iOS 3.2 that defines a shadowPath. This path does not need to follow the shape of the CALayer contents, so we can be a little more creative and go beyond the default relief shadow. In this case, we will build a cloud image that appears to cast its flattened shadow on the ground as it floats across the screen.
GraphicsGarden/PRPCloud.m
- (void)drawRect:(CGRect)rect {
CGFloat fullHeight = self.bounds.size.height;
CGPoint top = CGPointMake(0, 0);
CGPoint bottom = CGPointMake(0, fullHeight);
UIBezierPath *pPath = [self CreatePathWithHeight:
self.bounds.size.height];
[pPath addClip];
CGGradientRef gradient = [self gradientWithColor:self.innerColor
toColor:self.outerColor
count:2];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawLinearGradient(context,
gradient,
top,
bottom,
0);
pPath.lineWidth = self.lineThickness;
[self.strokeColor setStroke];
[pPath stroke];
pPath = [self CreatePathWithHeight:self.bounds.size.height/2.0];
self.layer.shadowPath = pPath.CGPath;
if (!self.shadowDistance) shadowDistance = self.bounds.size.height*1.8;
self.alpha = 0.9;
self.layer.shadowOffset = CGSizeMake(0, self.shadowDistance);
self.layer.shadowOpacity = 0.4;
}
PRPCloud is a subclass of PRPShapedView and therefore follows the same pattern as all the simple view objects in the Graphics Garden application. In the drawRect method, we build our cloud image using a UIBezierPath that is created in CreatePathWithHeight (shown next) from a C array of relative points. The CreatePathWithHeight method uses the height parameter to adjust the position of the points as the path is built.
GraphicsGarden/PRPCloud.m
- (UIBezierPath *) CreatePathWithHeight:(CGFloat)h {
CGFloat w = self.bounds.size.width;
CGFloat points[] =
{
0.4, 0.2,
0.5, 0.1, 0.6, 0.2,
0.8, 0.2, 0.8, 0.4,
0.9, 0.5, 0.8, 0.6,
0.8, 0.8, 0.6, 0.8,
0.5, 0.9, 0.4, 0.8,
0.2, 0.8, 0.2, 0.6,
0.1, 0.5, 0.2, 0.4,
0.2, 0.2, 0.4, 0.2,
};
CGPoint point;
CGPoint cPoint;
UIBezierPath *pPath = [UIBezierPath bezierPath];
point = CGPointMake(points[0]*w, points[1]*h);
[pPath moveToPoint:point];
for (int i = 2; i < sizeof(points)/sizeof(float); i+=4) {
cPoint = CGPointMake(points[i]*w, points[i+1]*h);
point = CGPointMake(points[i+2]*w, points[i+3]*h);
[pPath addQuadCurveToPoint:point controlPoint:cPoint];
}
[pPath closePath];
return pPath;
}
Consequently, we need to call the CreatePathWithHeight in two places—first to create the path for the cloud and then for the shadowPath property but with half the original height. The shadowOffset of the layer is set to the value of the shadowDistance property of the cloud view, placing it far enough below the cloud to give the impression that it’s at ground level.
You could also create a shadow using an additional CALayer, but that would require the extra step of synchronizing the position of the two layers whenever you needed to move or animate them.
Recipe 28 Display Animated Views
Problem
You can use Core Graphics to create a series of UIImageViews that represent various stages of a moving object. How do you display them in sequence to create a looping animation?
Solution
We’ve addressed animation in some detail in the recipes in this section, working mostly with manipulating the position or rotation of layers. Classic cell animation, like the kind seen in cartoons, uses a different technique that involves displaying a sequence