iOS Recipes - Matt Drance [49]
* * *
The Core Graphics library provides a set of C functions that allows us to modify the graphic context properties, such as color and line thickness, and also to create points, lines, or curves that can then be stroked or drawn to the graphics context. We can also fill the area inside any drawn shape with the current color.
UIBezierPath, introduced in iOS 3.2, encapsulates much of the Core Graphics drawing functions into Cocoa methods and in doing so largely obviates the need to reference and manipulate graphics contexts. Unfortunately, in iOS, gradients still live very much in the world of C functions and contexts, but it is possible to make the two techniques work together (see Figure 29, Shapes made from bezier curves ).
We can use the UIBezierPath class to create a path from a series of lines or curves. An initial point is added first, and then each line is added to the end of the previous one. If we intend to fill the path, we must ensure that it is closed, either by adding a line that leads back to the start point or by using the closePath method. The really powerful feature of the UIBezierPath class is, as its name implies, the bezier curves. There are two methods we can use: addCurveToPoint:controlPoint1:controlPoint2: and addQuadCurveToPoint:controlPoint:. As you can see, the QuadCurve, or quadratic bezier, is the simpler of the two and requires only a single control point, whereas the default Curve, or cubic bezier, needs two control points but can create significantly more complex curves.[2]
A clipping path defines the area where the contents of the graphics context will be visible; anything outside of the path will not be rendered to the screen. A UIBezierPath can be made to act as a clipping path by calling the addClip method. In using this technique, a gradient, which would otherwise cover the entire view, will be rendered only inside the clip region defined by the edges of our shape.
GraphicsGarden/PRPShapedView.m
-(CGGradientRef) gradientWithColor:(UIColor *)color
toColor:(UIColor *)color2
count:(CGFloat)colorCount
{
const CGFloat *colorParts = CGColorGetComponents(color.CGColor);
CGFloat red = colorParts[0];
CGFloat green = colorParts[1];
CGFloat blue = colorParts[2];
const CGFloat *color2Parts = CGColorGetComponents(color2.CGColor);
CGFloat red2 = color2Parts[0];
CGFloat green2 = color2Parts[1];
CGFloat blue2 = color2Parts[2];
CGFloat graduations[] =
{
red, green, blue, 1.0,
red2, green2, blue2, 1.0,
red, green, blue, 1.0
};
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient =
CGGradientCreateWithColorComponents(rgb,
graduations,
NULL,
colorCount);
CGColorSpaceRelease(rgb);
[(id)gradient autorelease];
return gradient;
}
Each of our shapes shares a common set of attributes and methods, so it makes sense to break those elements out into a base class, PRPShapedView. This class declares several properties—lineThickness and strokeColor—as well as innerColor and outerColor properties, which are needed when we create the gradient. It also defines the gradientWithColor method, which creates the CGGradient based on the innerColor and outerColor properties.
The gradientWithColor method breaks up the UIColors into their RGB components and creates a C array containing three sets of components. (The fourth element of each set is the opacity, which we set to 1, opaque.) The first and third elements use the color parameter, and the middle element is set to color2. This gives us the flexibility to use the count to specify a gradient of the first two colors or three colors using the primary color as both the start and end color. From the CGGradient returned, each subclass can choose to render a linear or radial gradient in the view.
GraphicsGarden/PRPetal.m
- (void)drawRect:(CGRect)rect {
CGFloat halfHeight = self.bounds.size.height/2;
CGFloat halfWidth = self.bounds.size.width/2;
CGFloat fullHeight = self.bounds.size.height;
CGFloat fullwidth = self.bounds.size.width;
CGPoint startPoint = CGPointMake(halfWidth, 3);
CGPoint midPoint