Learn Objective-C: A Quartz Primer
Overview
The iOS platform includes a powerful API that lets you directly draw content to the screen. The Core Graphics, or Quartz, framework was introduced in the early versions of Mac OS X, and was included in the iOS SDK. Quartz is a vector drawing API, which means that the drawing code is independent of the pixels on the screen. This is in contrast to usual image files, which has a finite number of pixels. The end result is that graphics drawn with Quartz will look especially sharp on Retina Displays, without resorting to additional resource files.
From Apple’s official documentation, “Quartz 2-D is an advanced, two-dimensional drawing engine available for iOS application development…. Quartz 2-D provides low-level, lightweight 2-D rendering with unmatched output fidelity regardless of display or printing device. Quartz 2-D is resolution- and device-independent; you don’t need to think about the final destination when you are using the Quartz 2-D…API for drawing.” We will talk about what this actually means throughout the course of this article.
Stocks app: Custom drawing
Quartz is used to do a lot of custom drawing across iPhone apps and can distinguish apps and their custom interfaces. For example, the Stocks app uses to draw the graphs the reflect the stock price over a specific interview. It is not possible to ship the app with every possible graph already drawn out as a regular PNG image; the app has to create the graphics itself. It does this using the Quartz APIs.
The Quartz API is a C API, which means that you won’t be working with Objective-C method calls. All the code you write for Quartz will consist of C functions. Don’t be afraid of C though; the API is not any harder to use than the Objective-C stuff that you should be familiar with.
How Quartz Works
Two fundamental patterns govern most Quartz functions. Quartz, like many other rendering engines, is state-based. Generally, you set some values, such as color or line-width, and those same values continue to be used until you set other values. In addition, Quartz uses the artist’s model to layout contents. This simply means that content drawn first will appear underneath newer content; in other words, new content can obscure older content. Therefore, the order in which you draw things matters.
Most Quartz functions are relative to a specific context. A context is simply a virtual canvas which you can draw to. This could be the screen, a PDF, a bitmap image file, or a custom context. Most Quartz functions take a context as an argument, to know which context to draw to. In fact, this is how printing works: on-screen content is rendered to a printer context (often a PDF), and then send to the printer. This was a technological breakthrough when it was invented— to be able to use the same code to draw on-screen as to the printer.
There are three general types of functions in Quartz. You begin by drawing to the context. This creates an abstract, transparent representation in the context. At this point, there is a data structure in memory, but nothing is actually shown. You can then stroke and/or fill the content you’ve drawn to make it display on screen. This allows you to use Quartz as an underlying structure for more complex drawing, such as displaying text along a curve.
Quartz functionality should be immediately familiar to graphic designers. Nearly anything you can do with vector drawing programs, such as Adobe Illustrator, you can do with Quartz. You can, for example, set line width, line color, line endings (cap, butt, round), and various blend modes, to name a few of Quartz’s functions.
Drawing in a view begins in the drawRect: method, which is a method inherited from UIView. You put Quartz calls in drawRect:, or call drawing subroutines (additional functions that contain separate Quartz calls—splitting the drawing code across different things you might want to do). However, you should never call drawRect: directly; the system will call it as needed. If you want to force the system to redraw an area of the screen, you can call setNeedsDisplay on the view, and it will be redrawn according to the code in drawRect:.
Drawing Basics
Most drawing in Quartz begins with a graphics context. The simplest context is the “current” context, ostensibly the on-screen content. For iOS, you call UIGraphicsGetCurrentContext(), which returns an object of type CGContextRef. You should save this context in a local variable for all subsequent drawing calls:
CGContextRef context = UIGraphicsGetCurrentContext();
If you have additional functions that draw additional context (generally known as drawing subroutines), you should pass in the context as an argument to the function, then push the context on the graphics stack. You should also pop it when done. This allows you to keep a separate version of the context to work with in the subroutine, so changes you make, such as setting fill color or stroke style, don’t affect the state in any other function, including in drawRect: itself. In addition, the system automatically sets up the context each time drawRect: is called. No guarantee is made about the state of the context across multiple method calls; therefore, you should not save the context in a local variable. Simply call UIGraphicsGetCurrentContext()
at the beginning of drawRect:
.
Quartz, as you might imagine, is primarily based on x-y points. All points are positive (unless you do some custom mapping for whatever reason). On the iPhone, the point (0,0) is the top-left point of the view, which follows the convention of web design and makes sense in many cases. Quartz on the desktop uses the reverse—like in geometry textbooks, (0,0) is located at the bottom-left point. This means that, without any modification of points, Quartz code from the iPhone will appear upside-down on the desktop, and the opposite is true as well.
All drawing in Quartz begins with rectangles. They are represented by the CGRect structure, containing an origin point and a size; the former is comprised of an x- and y-value float, while the latter is composed of a width and height. You can then stroke or fill the rectangles directly. To draw other shapes, you can either define a free-form path, or start with a bounding rectangle. For example, you could draw a circle by starting with a square (which is a rectangle) without a stroke or fill, and then call the function CGContextFillEllipseInRect()
if you want a filled circle, or CGContextStrokeEllipseInRect()
if you want an outline.
But thinking outside the box, you are allowed to draw any shape you’d like in Quartz, ranging from triangles to intricate curves. You do this by defining paths. For example, to draw a triangle you could use something like this:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath(context);
CGContextMoveToPoint(context, 100, 20);
CGContextAddLineToPoint(context, 150, 75);
CGContextAddLineToPoint(context, 50, 75);
CGContextClosePath(context); // Draws a straight line between first and last point
This code segment defines a triangle in memory. However, for anything to appear on screen you’ll have to do a bit more setup.
[[UIColor greenColor] setFill];
CGContextSetStrokeColor(context, [UIColor blackColor].CGColor);
CGContextDrawPath(context, kCGPathFillStroke);
Quartz takes typedef
‘d CGColor
s, but the object-oriented UIColor works well with Quartz. Instances of UIColor have a setFill and setStroke method, which are used to set the stroke and fill in Quartz contexts. You can also use the Quartz function calls and get a CGColor
from a UIColor
instance using the CGColor
property. Finally, there are a number of ways to fill and/or stroke paths. In this case, you pass in the context, and a preprocessor constant that tells Quartz to both fill and stroke.
Quartz constants generally begin with ‘k’ and follow camel case conventions.
Quartz has functions to draw text and images, but UIKit provides classes that handle this for you. UILabel
and UIImageView
are much easier to work with than using Quartz to render text or images; unless you need exact control over the layout, consider using one of those classes instead.
Drawing to a Close
Quartz is an incredibly powerful 2D drawing engine. Over the coming weeks (when I can get my system set up again), we’ll explore code examples and explore ways to leverage Quartz to its maximum potential.
This post is part of the Learn Objective-C in 24 Days course.
Previous Lesson | Next Lesson |