Adding Frameworks to an Xcode Project


A lot of Xcode projects require you to add additional frameworks to link against. Here’s how:

  1. Select the main project listing in the left column.
    Select Project

    Select Project

  2. Select Build Phases from the tabs near the top.
    Build Phases Tab

    Build Phases Tab

  3. Click the ‘+’ button in the “Link Binary With Libraries” section (you may have to twist it open.
    Select Frameworks

    Select Frameworks

  4. Choose the framework(s) you want to add, and click the “Add” button.
    Add Frameworks

    Add Frameworks

The Jungle, Part 7: Quartz Demos (Section 2 of 3)


This section will continue from where we left off last week. We’ll work with solid fills, gradient fills, and image and pattern fills. Open up the project from last week, and let’s get started. Navigate to CustomView.m

Single Color Fills

In the last section we filled our paths with solid fill colors. In those cases, we started with a color defined with UIColor. In some cases, however, you may want more control. Quartz’s underlying color structure is represented using a data type called CGColorRef (sometimes abbreviated to CGColor). You can create a UIColor with a CGColor and vice versa. UIColor has the

+colorWithCGColor:

method, and instances of UIColor have a .CGColor property. For example, to create a CGColor that represents a bright aqua color, we could use this code:

[UIColor colorWithRed:0 green:0.5 blue:1].CGColor;

To fill a rectangle with this color, we could use the following code:

CGContextSetFillColorWithColor(context, [UIColor colorWithRed:0 green:0.5 blue:1 alpha:1].CGColor);
CGContextFillRect(context, CGRectMake(20, 30, 80, 100));

Remember the state-based nature of Quartz—You set a color, or a certain style, then use it. We set a fill color, then use it to paint a rectangle using CGContextFillRect(). There is also CGContextStrokeRect().

That’s really all there is to single-color fills. You set a color, and then you fill a shape or path.

Gradient Fills

A gradient fill is a fill for a shape that transitions through two or more colors. Most gradient fills are linear, where the colors fade across the entire shape along a straight line, or radial, where they fade across a radius, and the colors form concentric rings. Less common is the circular gradient, where colors transition around a circle. We’ll look at the first two in this section.

Linear Gradient

A linear or axial gradient “varies along an axis between two defined end points. All points that lie on a line perpendicular to the axis have the same color value.” Let’s look at an example.

- (void)drawGradientFillsInContext:(CGContextRef)context {
	UIGraphicsPushContext(context);
	CGFloat colors[] = { 
        1.0, 1.0, 1.0, 1.0, 
        0.0, 0.5, 1.0, 0.8
    };
	
    CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2);
    CGColorSpaceRelease(baseSpace), baseSpace = NULL;
	
	CGRect rect = CGRectMake(50, 60, 100, 60);
    CGContextSaveGState(context);
    CGContextAddEllipseInRect(context, rect);
    CGContextClip(context);
	
    CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
    CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
	
    CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
    CGGradientRelease(gradient), gradient = NULL;
	
    CGContextRestoreGState(context);
	
    CGContextAddEllipseInRect(context, rect);
    CGContextDrawPath(context, kCGPathStroke);
	UIGraphicsPopContext();
}

We begin by defining the colors for our gradient. The colors are passed in as a C-style array of CGFloats consisting of color components. All colors are represented as four float values from 0.0 to 1.0, in the order Red-Green-Blue-Alpha. Here, we create a gradient that transitions from white to a partly-transparent version of the aqua color we saw above. We can create a gradient with more colors simply by adding more numbers to our colors array.

Next, we have to grab a color space, which basically is a representation of the color calibration. This is more useful when displaying the same thing across different color spaces, such as the difference between the screen and a printout. The next line is what we’re interested in—we get a Quartz gradient, of type CGGradientRef, by calling CGGradientCreateWithColorComponents(). This function takes four arguments. The first is the color space you got back. The second is an array of the component colors. This array should have as many items as the product of the fourth argument and the number of components the color space specifies. In this case, that would be 4. The third argument is the relative location of the colors in the gradient. Each CGFloat value must be between 0 and 1, and those values represent the location of the corresponding color in the gradient. For example, if you had four colors and you passed in [0, 0.1, 0.2, 1], the first three colors will be clustered at the start, 10% of the way along the gradient, and 20% of the way along the gradient. The last color would be at the end of the gradient. If you pass in NULL for this argument, as the code above does, the first color is assigned to location 0 (the start of the gradient), the last color is assigned to location 1 (the end of the gradient), and all other colors are equally spaced in between. The final argument is a count of the number of colors.

In our code example, after we create the gradient, we release the color space. Quartz has its own memory management system, because we’re not dealing with regular Objective-C objects.

In the next block of code, we create a rectangle to draw with. We then call CGContextSaveGState(). This is similar to pushing and popping the graphics context, except that each context has its own state of graphics states. By pushing a new graphics state, we constrain our gradient operations to only the shape we’re about to draw. We then draw the ellipse, and call CGContextClip() to clip or constrain the gradient to the outline of the shape.

Next, we set the start and end point of the gradient. This creates a line that the gradient follows; the location of the points determines the angle of the line, and consequently the angle of the gradient. In this case, we’re creating a vertical line down the center of the shape, from the top to the bottom. We then call CGContextDrawLinearGradient(), which takes five arguments. The first is the graphics context. The second is the CGGradient object we created earlier. The next two are the start and end points, and the last one is an integer that determines whether to draw the gradient’s end colors beyond the end points.

Here is another example:

CGFloat rainbowColors[] = {
		1.0, 0.0, 0.0, 1.0,
		1.0, 0.5, 0.0, 1.0,
		1.0, 1.0, 0.0, 1.0,
		0.0, 1.0, 0.0, 1.0,
		0.0, 1.0, 0.5, 1.0,
		0.0, 0.0, 1.0, 1.0,
		1.0, 0.0, 1.0, 1.0
	};
	CGFloat locations[] = {0, 0.3, 0.4, 0.5, 0.6, 0.7, 0.85};
	CGGradientRef rainbow = CGGradientCreateWithColorComponents(baseSpace, rainbowColors, locations, 7);
	// CGColorSpaceRelease(baseSpace), baseSpace = NULL;
	CGRect square = CGRectMake(160, 20, 140, 140);
	CGContextSaveGState(context);
	CGContextAddRect(context, square);
	CGContextClip(context);
	startPoint = CGPointMake(160, 160);
	endPoint = CGPointMake(300, 20);
	CGContextDrawLinearGradient(context, rainbow, startPoint, endPoint, 0);
	CGGradientRelease(rainbow), rainbow = NULL;
	CGContextRestoreGState(context);
	CGContextAddRect(context, square);
	CGContextDrawPath(context, kCGPathStroke);

Here, we draw a diagonal rainbow.

Rainbow Gradient

Rainbow Gradient

Radial Gradient

A radial gradient “is a fill that varies radially along an axis between two defined ends, which typically are both circles. Points share the same color value if they lie on the circumference of a circle whose center point falls on the axis. The radius of the circular sections of the gradient are defined by the radii of the end circles; the radius of each intermediate circle varies linearly from one end to the other.” One of the ends may be a single point rather than a circle (a point is simply a circle with a radius of 0). If one circle is partly or completely outside the other, you will end up with a cone- or cylinder-like shape.

The code for drawing a radial gradient is similar to drawing a linear gradient. You define locations and color components and then create a CGGradientRef with a color space. You then call CGContextDrawRadialGradient() to draw the actual gradient. Let’s see an example:

CGFloat redBallColors[] = {
		1.0, 0.9, 0.9, 0.7,
		1.0, 0.0, 0.0, 0.8
	};
	CGFloat glossLocations[] = {0.05, 0.9};
	CGGradientRef ballGradient = CGGradientCreateWithColorComponents(baseSpace, redBallColors, glossLocations, 2);
	CGRect circleBounds = CGRectMake(20, 250, 100, 100);
	startPoint = CGPointMake(50, 270);
	endPoint = CGPointMake(70, 300);
	CGContextDrawRadialGradient(context, ballGradient, startPoint, 0, endPoint, 50, 0);
	CGContextAddEllipseInRect(context, circleBounds);
	CGContextDrawPath(context, kCGPathStroke);

This code will draw a glossy red ball:

Glossy Red Ball

Glossy Red Ball

In this code, we define the colors and gradient just as before. However, when drawing a radial gradient we don’t need to clip to a shape, unless we wanted to; in this case, we’re drawing a ball, so the default circular shape is fine for our needs. CGContextDrawRadialGradient() takes 7 arguments. The first two are the context and the gradient, same as before. The next two are the start point and the radius of the first circle—we’ll play with that next. The next two are the end point and the radius of the second circle; we set this at 50 to create a ball with a size of 100×100. The last argument is an integer specifying whether to draw beyond the bounds.

This example will draw a radial gradient background, clipped in a rectangle:

CGFloat backgroundColors[] = {
		0.3, 0.3, 0.3, 1.0,
		0.1, 0.1, 0.1, 1.0
	};
	CGGradientRef backgroundGradient = CGGradientCreateWithColorComponents(baseSpace, backgroundColors, NULL, 2);
	CGContextSaveGState(context);
	CGRect backgroundRect = CGRectMake(20, 150, 80, 50);
	CGContextAddRect(context, backgroundRect);
	CGContextClip(context);
	startPoint = CGPointMake(CGRectGetMidX(backgroundRect), CGRectGetMidY(backgroundRect));
	CGContextDrawRadialGradient(context, backgroundGradient, startPoint, 0, startPoint, 35, kCGGradientDrawsAfterEndLocation);
	CGContextRestoreGState(context);
	CGContextAddRect(context, backgroundRect);
	CGContextDrawPath(context, kCGPathStroke);

Here, we see a radial gradient constrained in a rectangle. The gradient’s radius doesn’t take it all the way to the edge of the rectangle, so the rest of the rectangle is filled with the end color. This is specified by the kCGGradientDrawsAfterEndLocation parameter passed to CGContextDrawRadialGradient().

Let’s look at one last example:

[[UIColor colorWithRed:0 green:0.5 blue:0 alpha:0.5] setStroke];
	CGContextAddEllipseInRect(context, CGRectMake(180, 180, 100, 100));
	CGContextDrawPath(context, kCGPathStroke);
	CGFloat coneColors[] = {
		0.2, 0.8, 0.2, 1.0,
		1.0, 1.0, 0.9, 0.9
	};
	CGGradientRef coneGradient = CGGradientCreateWithColorComponents(baseSpace, coneColors, NULL, 2);
	startPoint = CGPointMake(230, 230);
	endPoint = CGPointMake(280, 330);
	CGContextDrawRadialGradient(context, coneGradient, startPoint, 50, endPoint, 10, 0);
	CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:0.1 green:0.6 blue:0.1 alpha:0.3].CGColor);
	CGContextAddEllipseInRect(context, CGRectMake(270, 320, 20, 20));
	CGContextDrawPath(context, kCGPathStroke);
Quartz Cone

Quartz Cone

Because the circles are partly outside of each other, Quartz draws a cone figure.

Image and Pattern Fills

To make more complicated designs, sometimes it’s easier to use a pre-rendered image as a fill. You may also want to repeat (tile) it to fit the area, rather than stretching it and loosing quality.

We start by loading an image into a shape, and by clipping to the shape we can use it as a fill:

CGRect ellipseRect = CGRectMake(20, 30, 100, 80);
	CGContextSaveGState(context);
	CGContextAddEllipseInRect(context, ellipseRect);
	CGContextClip(context);
	[[UIImage imageNamed:@"Image Fill.jpg"] drawInRect:ellipseRect];
	CGContextRestoreGState(context);
	CGContextAddEllipseInRect(context, ellipseRect);
	CGContextDrawPath(context, kCGPathStroke);

This code is mostly old stuff. We have to clip to a shape to constrain the image to that shape; otherwise the whole image would be drawn in the entire rect. Larger images would in fact be drawn at full size, and could cover the whole screen and go beyond. We can draw the image using a method built into UIImage. -drawInRect: takes a CGRect as its only parameter, and draws into that rectangle, filling it unless a clipping path has been defined. There is a similar way to tile images:

CGRect tileRect = CGRectMake(150, 40, 150, 110);
	CGContextSaveGState(context);
	CGContextAddEllipseInRect(context, tileRect);
	CGContextClip(context);		// Clip; otherwise whole rect will be drawn
	[[UIImage imageNamed:@"TileImage.png"] drawAsPatternInRect:tileRect];
	CGContextRestoreGState(context);
	CGContextAddEllipseInRect(context, tileRect);
	CGContextDrawPath(context, kCGPathStroke);

This code should be self-explanatory. The results:

Quartz Pattern and Image  Fills

Quartz Pattern and Image Fills

In the next section, we’ll look at more advanced things we can do with Quartz and related frameworks, including Core Animation.

The Jungle, Part 7: Quartz Demos (Section 1 of 3)


It’s been a while since we last plunged into the jungle. Previously, I’ve written about Quartz, which is the 2D graphics engine that powers the graphics in iOS. In this post, we’ll take a look at various examples of Quartz at work. I’ll explain everything as we go along. Open up the demo app from last time, and let’s get started!

Adding Frameworks

First, we have to add the CoreGraphics and QuartzCore frameworks to our project. These frameworks will support the Quartz code we’ll be writing.

Custom Views

In iOS, you draw to a view, not a view controller. This means that if you want a view with custom drawing, you’ll have to create a subclass of UIView (or a subclass of one of UIKit’s views, such as UITableViewCell), and if you have a view controller managing that view, then you may have to do the necessary configuration in code or Interface Builder as well. In our demo app, we see that we have a view controller called CustomDrawingViewController. Right now, it’s just managing a stock UIView. We’ll need to subclass UIView to do our own drawing, so create a new file, call it CustomView, and make it a subclass of UIView. Switch to CustomView.m and edit the initWithFrame: method:

- (id)initWithFrame:(CGRect)frame {
    if (!(self = [super initWithFrame:frame]))
		return nil;
	self.backgroundColor = [UIColor lightGrayColor];
	return self;
}

Here, we’re just setting a custom background for our drawing view. Head over to CustomDrawingViewController.m, and set our custom view as the view controller’s view:

- (void)loadView {
    // Implement loadView to create a view hierarchy programmatically, without using a nib.
	CustomView *customView = [[CustomView alloc] initWithFrame:CGRectMake(0, 44, 320, 480)];
	self.view = customView;
}

We use loadView here in place of a xib file, because (in my opinion) two lines of code, which is all we will need here, is simpler than a whole file to manage. Make sure you import CustomView.h. If you run the app now and browse through the graphics demos table, you’ll see our custom view with the gray background.

Drawing a Plan

We’re going to use one custom view to handle all our drawing needs. We will tell the view what type of content to draw, and then we can use Quartz subroutines (a fancy word for methods/functions) to draw the actual content. We’ll set up an enumerated type for the different demos we’ll work with. In addition, there is a preferred way to work with Quartz subroutines; we’ll discuss this as well.

Writing the Setup Code

We’ll begin by declaring some constants that we will reference throughout our app. In Xcode, create a new file (Command-N). Under “C and C++” on the left, choose “Header File” and save it as “Constants.h”. Add the following content:

typedef enum {
	QuartzContentStraightLines,
	QuartzContentCurves,
	QuartzContentShapes,
	QuartzContentSolidFills,
	QuartzContentGradientFills,
	QuartzContentImageFills,
	QuartzContentSimpleAnimations,
	QuartzContentBounce,
	QuartzContentOther,
} QuartzContentMode;

In CustomView.h, import Constants.h and declare the following property:

@property (assign, nonatomic) QuartzContentMode mode;

Synthesize the property. Also add the same property to CustomDrawingViewController and synthesize it.

In CustomDrawingViewController, import Constants.h and declare and implement the following method:

- (id)initWithContentMode:(QuartzContentMode)contentMode {
	if (!(self = [super init]))
		return nil;
	self.mode = contentMode;
	return self;
}

In loadView, after initializing customView, add the following line:

customView.mode = self.mode;

To use this new property, we have to set it when we select a row in our table. Go to GraphicsTableViewController.m, and replace the tableView:didSelectRowAtIndexPath: method with this:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    CustomDrawingViewController *drawingVC = [[CustomDrawingViewController alloc] initWithContentMode:(QuartzContentMode)indexPath.section * 3 + indexPath.row];
	drawingVC.viewTitle = [[tableView cellForRowAtIndexPath:indexPath] textLabel].text;
	[self.navigationController pushViewController:drawingVC animated:YES];
}

Knowing that we have three rows in a section, we simply take the indexPath and use it to calculate an integer value that corresponds to our enumerated type (remember than enumerated types are basically specific names given to integer values) and pass it along.

In CustomView.m, add the following code:

#pragma mark - Drawing Methods
- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
	switch (self.mode) {
		case QuartzContentStraightLines:
			[self drawStraightLinesInContext:context];
			break;
		case QuartzContentCurves:
			[self drawCurvesInContext:context];
			break;
		case QuartzContentShapes:
			[self drawCustomShapesInContext:context];
			break;
		case QuartzContentSolidFills:
			[self drawSolidFillsInContext:context];
			break;
		case QuartzContentGradientFills:
			[self drawGradientFillsInContext:context];
			break;
		case QuartzContentImageFills:
			[self drawImageAndPatternFillsInContext:context];
			break;
		case QuartzContentSimpleAnimations:
			[self drawSimpleAnimationsInContext:context];
			break;
		case QuartzContentBounce:
			[self drawBouncesInContext:context];
			break;
		case QuartzContentOther:
			[self drawOtherInContext:context];
			break;
		default:
			break;
	}
}

Quartz Methods

For each subroutine, we have to push and pop a graphics context. Quartz is a state-based drawing system. If we didn’t push on a graphics context, and we made a state change, such as changing the stroke or fill color, you don’t want to be caught off-guard when you return to the calling method. This could be a bigger issue if the subroutine you’re using was written by someone else; it would be nearly impossible (and very impractical) to restore the state to the way it was before the method call without pushing and popping a graphic context.

In the beginning of the subroutine, we have the following line:

UIGraphicsPushContext(context);/sourcecode]
Where "context" is an argument passed into the subroutine.
At the end of the method, pop the context:
UIGraphicsPopContext();

Set up all the subroutines as such:

- (void)drawStraightLinesInContext:(CGContextRef)context {
	UIGraphicsPushContext(context);
	UIGraphicsPopContext();
}

- (void)drawCurvesInContext:(CGContextRef)context {
	UIGraphicsPushContext(context);
	UIGraphicsPopContext();
}

- (void)drawCustomShapesInContext:(CGContextRef)context {
	UIGraphicsPushContext(context);
	UIGraphicsPopContext();
}

- (void)drawSolidFillsInContext:(CGContextRef)context {
	UIGraphicsPushContext(context);
	UIGraphicsPopContext();
}

- (void)drawGradientFillsInContext:(CGContextRef)context {
	UIGraphicsPushContext(context);
	UIGraphicsPopContext();
}

- (void)drawImageAndPatternFillsInContext:(CGContextRef)context {
	UIGraphicsPushContext(context);
	UIGraphicsPopContext();
}

- (void)drawSimpleAnimationsInContext:(CGContextRef)context {
	UIGraphicsPushContext(context);
	UIGraphicsPopContext();
}

- (void)drawBouncesInContext:(CGContextRef)context {
	UIGraphicsPushContext(context);
	UIGraphicsPopContext();
}

- (void)drawOtherInContext:(CGContextRef)context {
	UIGraphicsPushContext(context);
	UIGraphicsPopContext();
}

Now let’s fill in those methods.

Drawing Lines

Drawing lines, or paths, as they are called in Quartz, begins with defining a path, moving and adding lines to points, and finishing the path. Then you can stroke and/or fill it to make it visible. Note that simply drawing and closing a path will make it exist in memory, but it will not actually be drawn on screen until you request a stroke or fill. We’ll start by drawing some lines and playing around with the settings.

- (void)drawStraightLinesInContext:(CGContextRef)context {
	UIGraphicsPushContext(context);
	CGContextBeginPath(context);
	CGContextMoveToPoint(context, 35, 10);
	CGContextAddLineToPoint(context, 45, 65);
	[[UIColor blueColor] setStroke];
	CGContextStrokePath(context);
	UIGraphicsPopContext();
}

If you build and run and navigate to the corresponding view, you’ll see a thin blue line in the top-left of the screen.

First Quartz Path

First Quartz Path

To draw this line, we first have to begin the path using CGContextBeginPath(). This function (most of Quartz is written in C using C-style functions, rather than Objective-C methods) allocates memory for a path and prepares the drawing system, but doesn’t actually do anything visible. Think of it as uncapping a pen. The next function is CGContextMoveToPoint(), which takes as arguments the context and an x- and y- coordinate. This sets the beginning point of the line—think of it as moving a pen over the paper and putting it down at a point. Then, we call CGContextAddLineToPoint() which also takes the context and an x- and y- coordinate. This function is analogous to dragging a pen over the page to the second point. We then specify blue as the stroke color. Here, we’re using a convenience method that UIColor provides rather than using the corresponding Quartz call. The result is the same, and unless you’re working with custom colorspaces it is much easier to just use the convenience methods. There is a corresponding -setFill method for setting the fill color of a path. Finally, we call CGContextStrokePath() to actually draw the path in our blue color. By default the stroke width is 1.0 point. We can change that with a simple function call:

CGContextSetLineWidth(context, 5.0);

Add this line right after the UIColor call. Build and run, and you’ll see that the line is now much thicker.

Add the following line to the code, right after the last line:

CGContextSetLineCap(context, kCGLineCapRound);

If you build and run now, you’ll see that the line’s ends are not squared off, but nicely rounded. In fact, it is defined as such:

Quartz draws a circle with a diameter equal to the line width around the point where the two segments meet, producing a rounded corner. The enclosed area is filled in.

The idea here is that to draw lines in Quartz, you begin by defining the path and its endpoints. Then you specify the stroke color and other stroke settings, then actually stroke it. So try drawing another line—make it red, and stretch diagonally down and to the left from the upper-right corner.

Something like this:

CGContextBeginPath(context);
	CGContextMoveToPoint(context, 250, 35);
	CGContextAddLineToPoint(context, 85, 130);
	[[UIColor redColor] setStroke];
	CGContextSetLineWidth(context, 2.0);
	CGContextSetLineCap(context, kCGLineCapSquare);
	CGContextStrokePath(context);

You can simply append this code right after the existing code, but before popping the context. Here I’m using a square line cap, defined as:

Quartz extends the stroke beyond the endpoint of the path for a distance equal to half the line width. The extension is squared off.

One other aspect of paths is a dashed path, which “allows you to draw a segmented line along the stroked path”. The path can be varied in complexity. Here are a few examples:

CGContextBeginPath(context);
	CGContextMoveToPoint(context, 55, 120);
	CGContextAddLineToPoint(context, 65, 220);
	[[UIColor greenColor] setStroke];
	CGContextSetLineWidth(context, 3.0);
	float lengths[] = {2.0, 6.0};
	CGContextSetLineDash(context, 0, lengths, 2);
	CGContextStrokePath(context);
	CGContextBeginPath(context);
	CGContextMoveToPoint(context, 105, 150);
	CGContextAddLineToPoint(context, 65, 290);
	[[UIColor blackColor] setStroke];
	CGContextSetLineWidth(context, 3.0);
	float lengths2[] = {7.5, 4.5, 1.0};
	CGContextSetLineDash(context, 3, lengths2, 3);
	CGContextStrokePath(context);
	CGContextBeginPath(context);
	CGContextMoveToPoint(context, 180, 120);
	CGContextAddLineToPoint(context, 260, 340);
	[[UIColor orangeColor] setStroke];
	CGContextSetLineWidth(context, 2.0);
	float lengths3[] = {5.0, 3.0, 4.0, 2.0, 3.0, 5.0, 2.0, 4.0, 1.0, 8.0, 1.0, 2.0, 1.0, 3.0, 1.0, 4.0, 1.0, 5.0};
	CGContextSetLineDash(context, 2, lengths3, 18);
	CGContextSetLineCap(context, kCGLineCapRound);
	CGContextStrokePath(context);

The function to set the dash is CGContextSetLineDash(). This takes four parameters. The first is the context. The second is an amount of offset—you can start a few pixels into the pattern which you specify in the third argument, which is a C-style array of floats. The fourth argument is the number of elements in that array.

One final point here: we’ve made a lot of changes to the graphics context, setting stroke colors, line caps, and dash patterns. If we didn’t push and pop the context, these changes would be used for the rest of the drawing in drawRect:, which is almost never what we want. It’s important to push and pop because it restores the calling context.

Drawing Curves

Curve geometry is more complex than straight lines, but the same path concepts apply. There are two main types of curves you can draw: arcs, which are segments of a circle, and Bézier curves, which are free-form curves defined by tangent points. Arcs are much easier to work with. Let’s look at an example:

- (void)drawCurvesInContext:(CGContextRef)context {
	UIGraphicsPushContext(context);
	CGContextBeginPath(context);
	//CGContextMoveToPoint(context, 25, 50);
	//CGContextAddLineToPoint(context, 50, 25);
	CGContextAddArc(context, 120, 120, 40, 0.25*M_PI, 1.5*M_PI, 0);
	[[UIColor redColor] setStroke];
	CGContextStrokePath(context);
	UIGraphicsPopContext();
}

Build and run, and you should see a portion of a circle in red on the screen.

Quartz Arc

Quartz Arc

You can also lead into the arc with a straight line:

CGContextBeginPath(context);
	CGContextMoveToPoint(context, 25, 50);
	CGContextAddLineToPoint(context, 120, 25);
	CGContextAddArc(context, 120, 120, 40, 0.25*M_PI, 1.5*M_PI, 0);
	[[UIColor redColor] setStroke];
	CGContextStrokePath(context);

Quartz will then draw a straight line from the end of your line to the start of the arc.

You define an arc by calling the function CGContextAddArc(). The first argument is the context. The next two are x- and y- coordinates for the center of the arc—the center point of the circle from which the arc is drawn. The fourth argument is the radius of the circle. The next two are the start angle and end angle, measured in radians where zero is horizontally to the right—the “positive x-axis”. The last argument is either a 0 or 1, where 0 is counterclockwise and 1 is clockwise.

Bézier curves are usually quadratic or cubic in nature, and are defined by a mathematical formula that act on the starting and ending points and one or more control points. From Apple’s documentation on Quartz:

The placement of the two control points determines the geometry of the curve. If the control points are both above the starting and ending points, the curve arches upward. If the control points are both below the starting and ending points, the curve arches downward. If the second control point is closer to the current point (starting point) than the first control point, the curve crosses over itself, creating a loop.

The curve that results is always tangental to the path that can be drawn between the starting and ending points, tracing through all the control points in order.

The following example draws three curves, and shows the control path for one of them:

CGContextBeginPath(context);
	CGContextMoveToPoint(context, 150, 100);
	CGContextAddQuadCurveToPoint(context, 250, 20, 300, 100);
	[[UIColor purpleColor] setStroke];
	CGContextStrokePath(context);

	CGContextBeginPath(context);
	CGContextMoveToPoint(context, 180, 220);
	CGContextAddQuadCurveToPoint(context, 300, 0, 310, 180);
	[[UIColor magentaColor] setStroke];
	CGContextStrokePath(context);

	CGContextBeginPath(context);
	CGContextMoveToPoint(context, 10, 260);
	CGContextAddCurveToPoint(context, 100, 160, 210, 360, 300, 290);
	[[UIColor greenColor] setStroke];
	CGContextStrokePath(context);

	// Draw control path for cubic curve
	CGContextBeginPath(context);
	CGContextMoveToPoint(context, 10, 260);
	CGContextAddLineToPoint(context, 100, 160);
	CGContextAddLineToPoint(context, 210, 360);
	CGContextAddLineToPoint(context, 300, 290);
	[[UIColor darkGrayColor] setStroke];
	CGContextSetLineWidth(context, 0.5);
	float lengths[] = {2.0, 1.0};
	CGContextSetLineDash(context, 0, lengths, 2);
	CGContextStrokePath(context);

The first two examples draw a quadratic Bézier curve with one control point. The function of interest is CGContextAddQuadCurveToPoint(). The first argument is the context, followed by the x- and y- coordinates of the control points and the x- and y- coordinates of the end point. Moving the control point can dramatically change the shape of the curve.

The third example is a cubic Bézier curve with two control points. CGContextAddCurveToPoint() takes the context, the x- and y- coordinates of the first control point, the x- and y- coordinates of the second control point, and the x- and y- coordinates of the end point. The control points pull the curve along; I’ve illustrated the control path formed by the control points in a dashed gray line. Of course, all the things you can do with regular paths apply to curves as well; in fact, think of curves as a path component that you can easily append to existing paths. You can chain multiple paths together just by beginning a path, adding lines and curves, and stroking it. Now let’s talk about closing paths.

Shaping Up

A shape is simply a closed path. You can make polygons with straight lines; this code will draw a triangle:

CGContextBeginPath(context);
	CGContextMoveToPoint(context, 75, 10);
	CGContextAddLineToPoint(context, 160, 150);
	CGContextAddLineToPoint(context, 10, 150);
	CGContextClosePath(context);
	[[UIColor redColor] setFill];
	[[UIColor blackColor] setStroke];
	CGContextSetLineWidth(context, 5.0);
	CGContextSetLineJoin(context, kCGLineJoinRound);
	CGContextDrawPath(context, kCGPathFillStroke);

Everything here we’ve seen before, except for CGContextClosePath() which automatically inserts a line from the last point to the first. In addition, CGcontextSetLineJoin() is similar to setting the line cap, but used for the intersection of two paths rather than the ends. This code results in something like this:

Quartz Shapes

Quartz Shapes

Quartz includes some convenient ways to draw rectangles, ellipses, and circles.

// Draw rectangle
	CGContextBeginPath(context);
	CGContextAddRect(context, CGRectMake(200, 45, 100, 63));
	[[UIColor yellowColor] setFill];
	[[UIColor greenColor] setStroke];
	CGContextSetLineWidth(context, 3.0);
	CGContextDrawPath(context, kCGPathFillStroke);
	
	// Stroke Ellipse
	CGContextBeginPath(context);
	CGContextAddEllipseInRect(context, CGRectMake(35, 200, 180, 120));
	[[UIColor blueColor] setStroke];
	CGContextDrawPath(context, kCGPathStroke);
	
	// Fill Circle
	CGContextBeginPath(context);
	CGContextAddEllipseInRect(context, CGRectMake(220, 150, 70, 70));
	[[UIColor orangeColor] setFill];
	CGContextDrawPath(context, kCGPathFill);

Quartz provides CGContextAddRect() to draw a CGRect struct. This is an easier way to draw a rectangle than manually calculating and adding lines to points. Quartz also provides CGContextAddEllipseInRect() which draws an ellipse in the rectangle, using filling the width and height of the rectangle; the rectangle itself does not get drawn. To draw a circle, pass in a rect that has the same width and height—a square.

Drawing other polygons is a bit harder. This code snippet (from a CS193P lecture) calculates an NSArray of vertex points for a polygon of a certain number of sides in a rect:

- (NSArray *)pointsForPolygonWithSides:(NSInteger)numberOfSides inRect:(CGRect)rect {
	CGPoint center = CGPointMake(rect.size.width / 2.0, rect.size.height / 2.0);
	float radius = 0.9 * center.x;
	NSMutableArray *result = [NSMutableArray array];
	float angle = (2.0 * M_PI) / numberOfSides;
	// float exteriorAngle = M_PI - ((2.0 * M_PI) / numberOfSides);
	float rotationDelta = angle - (0.5 * (M_PI - ((2.0 * M_PI) / numberOfSides)));
	
	for (int currentAngle = 0; currentAngle < numberOfSides; currentAngle++) {
		float newAngle = (angle * currentAngle) - rotationDelta;
		float curX = cos(newAngle) * radius;
		float curY = sin(newAngle) * radius;
		[result addObject:[NSValue valueWithCGPoint:CGPointMake(center.x + curX + rect.origin.x, center.y + curY + rect.origin.y)]];
	}
	return result;
}

To draw a heptagon (7-sided polygon), add that method to CustomView.m, and use this drawing code:

// Draw heptagon
	NSArray *points = [self pointsForPolygonWithSides:7 inRect:CGRectMake(230, 250, 70, 70)];
	CGContextBeginPath(context);
	CGPoint firstPoint = [[points objectAtIndex:0] CGPointValue];
	CGContextMoveToPoint(context, firstPoint.x, firstPoint.y);
	for (int index = 1; index < [points count]; index++) {
		CGPoint nextPoint = [[points objectAtIndex:index] CGPointValue];
		CGContextAddLineToPoint(context, nextPoint.x, nextPoint.y);
	}
	CGContextClosePath(context);
	[[UIColor magentaColor] setFill];
	[[UIColor blackColor] setStroke];
	CGContextDrawPath(context, kCGPathFillStroke);

In the next section, we’ll look at the next section of Quartz graphics.

  • Welcome

    My goal is to make CupsOfCocoa into a beautiful source for beginners to the iPhone platform to get started. Subscribe below for more, and stay tuned!

  • Contact Me

    If you need to contact me for any reason, feel free to send me an email.
  • The Giving Spirit

    If you've found this site helpful, would you consider donating a little sum? Any amount is appreciated...Thanks so much!

  • Roadmap

  • Enter your email address to follow this blog and receive notifications of new posts by email.

    Join 220 other followers

  • Back to the Past

    June 2012
    S M T W T F S
     12
    3456789
    10111213141516
    17181920212223
    24252627282930
  • Time Machine

  • You count!

    • 622,650 views
  • Worldwide Stats

    free counters
%d bloggers like this: