An Introduction to Quartz 2D

What Is Quartz 2D?

Quartz 2D is Apple's 2D drawing engine, an important component of the Core Graphics framework. You may often see Quartz 2D referred to as Core Graphics or simply CG.

Quartz 2D uses the "painter's model". In the painter’s model, each successive drawing operation applies a layer of “paint” to an output “canvas”, often called a page. Think of this as an artist working on a painting. If the artist were to paint the entire canvas blue and then paint some clouds onto the canvas then the clouds would cover the blue beneath them. Once something is "painted" onto the canvas, it cannot be changed but by adding more paint atop of it.

All drawing in Quartz 2D is done through a graphics context of type CGContextRef. With a reference to a graphics context, you can use the Quartz 2D functions to draw to the context, perform operations, such as translating the context, and change graphic state parameters, such as line width and fill color. Quartz 2D is a C-based API, as such you will invoke the C functions passing in the context as a parameter.

To draw to the screen on iOS, you must subclass a UIView and override its drawRect(_:)method. It is inside this drawRect(_:) method that you will do any custom drawing. You should never call the drawRect(_:) method directly in your code. If you need to update the screen with fresh drawing commands, you should call the methods setNeedsDisplay() or setNeedsDisplayInRect(_:).

When using Quartz 2D on iOS, the coordinate (0,0) is at the top left of the screen. The x coordinate increases as you move right and the y coordinate increases as you move down.

Throughout this tutorial, you may wish to consult the Quartz 2D programming guide. The purpose of this tutorial is to get started using Quartz 2D. There is much that will not be covered and to fully appreciate all that Quartz 2D has to offer I suggest you read the programming guide.

With this brief introduction out of the way, let's get started using Quartz 2D.

1. Preparing a UIView for Drawing

Assuming you have a project open and are ready to start working with Quartz 2D, the steps you need to take are fairly simple. You'll need to create a class that is a subclass of UIView, add a view from the Object library to your project in Interface Builder, and set that view's class to the UIView subclass you created. Let's go through this step by step.

Step 1: Subclassing UIView

Go to File > New > File.... Under the iOS section, select Source and then choose Cocoa Touch Class as the template.

New Cocoa Touch Class

On the screen that follows, give your class a name, make sure it's a UIView subclass, and set the language to Swift. Press Next and choose where to save your new class.

Set File Options

If you view the source of your newly created class, you will see the drawRect(_:) method. It's currently commented out, but we will change that in a few moments.

Step 2: Adding a View and Setting the Class

Open the project's storyboard and open the Object Library on the right. In the search field at the bottom, enter "UIView" to filter out objects we're not interested in.

Show Object Library

Drag a UIView instance onto the view controller. With the view selected, open the Identity Inspector on the right and change Class to whatever you named the subclass.

Set Class on View

Any code you add inside the drawRect(_:) method will be drawn when the UIView subclass is instantiated. The view automatically configures the drawing environment so that you can begin drawing immediately. The view configures the CGContextRef mentioned at the beginning of this lesson and it is inside the drawRect(_:) method that you will get a reference to it.

2. Starter Project

To get us started quickly, I have provided a starter project that has all the views already wired up and ready to use. The UIView subclasses are named after the drawing command we will be exploring. For example, when we are learning about drawing lines the corresponding class will be named DrawLinesView.

You can download the starter project from GitHub. We will get started coding in the next step.

3. Obtaining a Reference to the Graphics Context

Before you can do any drawing, you need to get a reference to the graphics context. This is accomplished as follows.

This returns an opaque type, CGContextRef, and you will pass this context into the C functions to do the custom drawing. Now that we know how to get a reference to the graphics context we can start exploring the drawing commands.

4. Drawing a Line

If you've downloaded the starter project, open DrawLineView.swift and add the following to the drawRect(_:) method.

We first get a reference to the drawing context as discuss earlier. Because this is something we will do for every example, I won't mention this in the coming examples.

The CGContextSetStrokeColorWithColor(_:_:) function sets the color with which the line will be drawn or stroked. The parameters we pass in are the graphics context and the new stroke color.

If you think of the graphics context as the canvas of a painter, then the CGContextMoveToPoint(_:_:_:) function moves the paintbrush to a particular point on the canvas from which to begin or continue drawing. Imagine drawing on a piece of paper, lifting your hand, and moving to a different part of the paper and continuing to draw. Essentially that is what this method accomplishes. We pass in the graphics context and an x and y coordinate to start the drawing from.

The CGContextAddLineToPoint(_:_:_:) function takes as parameters the graphics context, the x value for the end of the line segment, and the y value for the end of the line segment. After adding the line segment, the current point will be set to the endpoint of the line segment. We started the drawing operation at (0,0), after this drawing operation the cursor or paintbrush is at (200,200).

Finally, to do the actual drawing you need to call the CGContextStrokePath(_:) function passing in the graphics context.This function simply draws a line along the path we specified.

Build and run the sample project to see the effect.

5. Drawing a Rectangle

Open DrawRectangleView.swift and add the following to the drawRect(_:) method. You should be familiar with the first two lines by now.

The CGRectMake(_:_:_:_:) function is part of CGGeometry and provides an easy way to create a CGRect structure. As its name implies, CGRect  is a structure that contains the location and dimensions of a rectangle. A CGRect has two fields, origin and size, which are a CGPoint and a CGSize respectively. If you are not familiar with these data types, then have a quick read in the CGGeometry reference.

We create a constant, rectangle, using the CGRectMake(_:_:_:_:) function and call the CGContextAddRect(_:_:) function, which takes as parameters the graphics context and a CGRect. Lastly, we call CGContextStrokePath(context) to draw the rectangle.

Build and run the project to see the rectangle drawn to the screen.

6. Drawing a Circle

Open DrawCircleView.swift and update the drawRect(_:) method as follows.

You may be wondering why we are calling CGRectMake(_:_:_:_:) when we are drawing a circle? The rectangle is the area the circle must fit within. In the code above, we create a circle by using a square. If you want to draw an oval or ellipse, then you need to make the rectangle more rectangular in shape.

We then call the CGContextAddEllipseInRect(_:_:) function, which takes as parameters the graphics context and the rectangle into which to draw the ellipse. The circle is drawn by calling CGContextStrokePath(_:), passing in the graphics context.

7. Drawing an Arc

Open DrawArcView.swift and add the following code inside the drawRect(_:) method.


The CGContextAddArc(_:_:_:_:_:_:_:) function takes quite a few parameters:

  • the graphics context
  • the x value for the center of the arc
  • the y value for the center of the arc
  • the arc's radius
  • the angle to the starting point of the arc, measured in radians from the positive x-axis
  • the angle to the end point of the arc, measured in radians from the positive x-axis
  • a value of 1 to create a clockwise arc or a value of 0 to create a counterclockwise arc

8. Drawing a Path

To draw more complex shapes, you create a path and stroke it. Take a look at the drawRect(_:) method in DrawPathView.swift.

In the drawRect(_:) method, we call CGContextAddLineToPoint(_:_:_:) a number of times to create a triangle. Note that the triangle is not filled, only stroked. In the next step, we will see how to fill the triangle with color.

9. Filling a Path

Open FillPathView.swift and update the drawRect(_:) method as shown below.

In the previous step, we stroked a path, but you can also fill a path with a particular color. In the above drawRect(_:) method, we start by creating a path for the same triangle as in the previous example. This time we set a fill color using the CGContextSetFillColorWithColor(_:_:) function and call CGContextFillPath(_:) rather than CGContextStrokePath(_:).

10. Filling an Ellipse

Aside from filling paths, you can also fill ellipses and rectangles. In this example, we will fill an ellipse. Filling a rectangle, however, is very similar. Documentation will tell you how that's done. Update the drawRect(_:) method in FillEllipseView.swift as shown below.

Most of this code should be familiar by now. We are using a new function, CGContextSetLineWidth(_:_:), to set the line width and we call CGContextFillEllipseInRect(_:_:) to fill the ellipse. This function takes as parameters the graphics context and the rectangle in which to fill the ellipse.

11. Adding Lines

The CGContextAddLines(_:_:_:) function is a handy function when you have a number of connected straight line segments you wish to draw. Here we recreate the triangle from earlier in the examples, using the CGContextAddLines(_:_:_:) function. Add the following code to AddLinesView.swift.

The CGContextAddLines(_:_:_:) function takes as parameters the graphics context, an array of values that specify the start and end points of the line segments to draw as CGPoint structures, and the number of elements in the array. Note that the first point in the array specifies the starting point.

12. Drawing a Gradient

With Quartz 2D, it's easy to draw gradients. Both linear and radial gradients are supported. In this example, we will draw a linear gradient. The documentation will help you if you're interested in drawing radial gradients. Add the following to DrawGradientView.swift.

The CGContextDrawLinearGradient(_:_:_:_:_:) function takes as parameters:

  • the graphics context
  • a CGGradient structure
  • a start point
  • an end point
  • option flags that specify whether the fill is extended beyond the starting or ending point

A CGGradient structure defines a smooth transition between colors across an area. It has a color space, two or more colors, and a location for each color. The constants colorspace, colors, and locations in the above example represent these parts that make up the CGGradient.

To draw the gradient, we call the CGContextDrawLinearGradient(_:_:_:_:_:) function, passing in the graphics context, the CGGradient, start and end values, and 0 to indicate that the fill should extend beyond the starting location.

13. Drawing a Shadow

A shadow is an image drawn underneath, and offset from, a graphics object such that the shadow mimics the effect of a light source cast on the graphics object. — Quartz 2D Programming Guide

 There are two functions you can use to draw shadows, CGContextSetShadow(_:_:_:) and CGContextSetShadowWithColor(_:_:_:_:). When using CGContextSetShadow(_:_:_:), all objects drawn are shadowed using a black color with 1/3 alpha. The CGContextSetShadowWithColor(_:_:_:_: function allows you to specify a color for the shadow.

Let's see how this works in practice. Add the following to SetShadowWithColor.swift.

When drawing shadows, you should save the state of the graphics context, make any required changes, and then restore the graphics state. We call CGContextSaveGState(_:) to save the current state of the graphics context, specify an offset for the shadow, shadowOffset, and call the CGContextSetShadowWithColor(_:_:_:_:) function. This functions takes as parameters:

  • the graphics context
  • the offset for the shadow
  • the blur amount
  • the color of the shadow

The rest of the code should be familiar to you. Lastly, we restore the graphics context by calling CGContextRestoreGState(_:), passing in the graphics context.

14. Drawing a Happy Face

I thought it would be fun to end this tutorial by drawing a simple happy face using what we have learned throughout this tutorial. Add the following to DrawHappyFaceView.swift.

The implementation of the drawRect(_:) method should make sense by now and you should have a happy face drawn to the screen.

Conclusion

This brings this tutorial to an end. You should now have a basic understanding of how to perform custom drawing using the Quartz 2D drawing engine. I hope you have learned something useful by reading this tutorial.

Tags:

Comments

Related Articles