Overview of Cocoa Drawing
Drawing is a fundamental part of most Cocoa applications. If your application uses only standard system controls, then Cocoa does all of the drawing for you. If you use custom views or controls, though, then it is up to you to create their appearance using drawing commands.
The following sections provide a quick tour of the drawing-related features available in Cocoa. Subsequent chapters provide more details about each feature, and also include examples for many common tasks you might perform during drawing.
Cocoa Drawing Support
The Cocoa drawing environment is available to all applications built on top of the Application Kit framework (
AppKit.framework). This framework defines numerous classes and functions for drawing everything from primitive shapes to complex images and text. Cocoa drawing also relies on some primitive data types found in the Foundation framework (
The Cocoa drawing environment is compatible with all of the other drawing technologies in OS X, including Quartz, OpenGL, Core Image, Core Video, Quartz Composer, PDF Kit, and QuickTime. In fact, most Cocoa classes use Quartz extensively in their implementations to do the actual drawing. In cases where you find Cocoa does not have the features you need, it is no problem to integrate other technologies where necessary to achieve the desired effects.
Because it is based on Quartz, the Application Kit framework provides most of the same features found in Quartz, but in an object-oriented wrapper. Among the features supported directly by the Application Kit are the following:
Path-based drawing (also known as vector-based drawing)
Like Quartz, the Cocoa drawing environment takes advantage of graphics hardware wherever possible to accelerate drawing operations. This support is automatic. You do not have to enable it explicitly in your code.
For information about the classes available in Cocoa, see AppKit Framework Reference and Foundation Framework Reference. For information on how to integrate C-based technologies into your Cocoa application, see Incorporating Other Drawing Technologies.
The Painter’s Model
Like Quartz, Cocoa drawing uses the painter’s model for imaging. In the painter’s model, each successive drawing operation applies a layer of “paint” to an output “canvas.” As new layers of paint are added, previously painted elements may be obscured (either partially or totally) or modified by the new paint. This model allows you to construct extremely sophisticated images from a small number of powerful primitives.
Figure 1-1 shows how the painter’s model works and demonstrates how important drawing order can be when rendering content. In the first result, the wireframe shape on the left is drawn first, followed by the solid shape, obscuring all but the perimeter of the wireframe shape. When the shapes are drawn in the opposite order, the results are very different. Because the wireframe shape has more holes in it, parts of the solid shape show through those holes.
The Drawing Environment
The drawing environment encompasses the digital canvas and graphics settings that determine the final look of your content. The canvas determines where your content is drawn, while the graphics settings control every aspect of drawing, including the size, color, quality, and orientation of your content.
The Graphics Context
You can think of a graphics context as a drawing destination. A graphics context encapsulates all of the information needed to draw to an underlying canvas, including the current drawing attributes and a device-specific representation of the digital paint on the canvas. In Cocoa, graphics contexts are represented by the
NSGraphicsContext class and are used to represent the following drawing destinations:
Windows (and their views)
Images (including bitmaps of all kinds)
Files (PDF, EPS)
By far, the most common drawing destination is your application's windows, and by extension its views. Cocoa maintains graphics context objects on a per-window, per-thread basis for your application. This means that for a given window, there are as many graphics contexts for that window as there are threads in your application. Although most drawing occurs on your application's main thread, the additional graphics context objects make it possible to draw from secondary threads as well.
For most other drawing operations, Cocoa creates graphics contexts as needed and configures them before calling your drawing code. In some cases, actions you take may create a graphics context indirectly. For example, when creating a PDF file, you might simply request the PDF data for a certain portion of your view object. Behind the scenes, Cocoa actually creates a graphics context object and calls your view's drawing code to generate the PDF data.
You can also create graphics contexts explicitly to handle drawing in special situations. For example, one way to create a bitmap image is to create the bitmap canvas and then create a graphics context that draws directly to that canvas. There are other ways to create graphics context objects explicitly, although most involve drawing to the screen or to an image. It is very rare that you would ever create a graphics context object for printing or generating PDF or EPS data.
For information about graphics contexts, see Graphics Contexts.
The Graphics State
In addition to managing the drawing destination, an
NSGraphicsContext object also manages the graphics state associated with the current drawing destination. The graphics state consists of attributes that affect the way content is drawn, such as the line width, stroke color, and fill color. The current graphics state can be saved on a stack that is maintained by the current graphics context object. Any subsequent changes to the graphics state can then be undone quickly by simply restoring the previous graphics state. This ability to save and restore the graphics state provides a simple way for your drawing code to return to a known set of attributes.
Cocoa manages some attributes of the graphics state in a slightly different way than Quartz does. For example, the current stroke and fill color are set using the
NSColor class, and most path-based parameters are set using the
NSBezierPath class. This shift of responsibility reflects the more object-oriented nature of Cocoa.
For more information about the attributes that comprise the current graphics state, and the objects that manage them, see Graphics State Information.
The Coordinate System
The coordinate system supported by Cocoa is identical to the one used in Quartz-based applications. All coordinates are specified using floating-point values instead of integers. Your code draws in the user coordinate space. Your drawing commands are converted to the device coordinate space where they are then rendered to the target device.
The user coordinate space uses a fixed scale for coordinate values. In this coordinate space, one unit is effectively equal to 1/72 of an inch. Although it seems like this might imply a 72 dots-per-inch (dpi) resolution for drawing, it is a mistake to assume that. In fact, the user coordinate space has no inherent notion of pixels or dpi. The use of floating-point values makes it possible for you to do precise layout in the user coordinate space and let Cocoa worry about converting your coordinates to the device space.
As the name implies, the device coordinate space refers to the native coordinate space used by the target device, usually a monitor or printer. Unlike the user coordinate space, whose units are effectively fixed, the units of the device coordinate space are tied to the resolution of the target device, which can vary. Cocoa handles the conversion of coordinates from user space to the device space automatically during rendering, so you rarely need to work with device coordinates directly.
For more information about coordinate systems in Cocoa, see Coordinate Systems Basics.
A transform is a mathematical construct used to manipulate coordinates in two-dimensional space. Transforms are used extensively in graphics-based computing to simplify the drawing process. Coordinate values are multiplied through the transform's mathematical matrix to obtain a modified coordinate that reflects the transform's properties.
In Cocoa, the
NSAffineTransform class implements the transform behavior. You use this class to apply the following effects to the current coordinate system:
You can combine the preceding effects in different combinations to achieve interesting results. During drawing, Cocoa applies the effects to the content you draw, imparting those characteristics on your shapes and images. Because all coordinates are multiplied through a transform at some point during rendering, the addition of these effects has little effect on performance. In fact, manipulating your shapes using transforms is often faster than manipulating your source data directly.
For more information about transforms, including how they affect your content and how you use them, see Coordinate Systems and Transforms.
Color and Color Spaces
Color is an important part of drawing. Before drawing any element, you must choose the colors to use when rendering that element. Cocoa provides complete support for specifying color information in any of several different color spaces. Support is also provided for creating colors found in International Color Consortium (ICC) and ColorSync profiles.
Transparency is another factor that influences the appearance of colors. In OS X, transparency is used to render realistic-looking content and aesthetically appealing effects. Cocoa provides full support for adding transparency to colors.
In Cocoa, the
NSColorSpace classes provide the implementation for color objects and color space objects. For more information on how to work with colors in Cocoa, see Color and Transparency.
Basic Drawing Elements
The creation of complex graphics often has a simple beginning. In Cocoa, everything you draw is derived from a set of basic elements that you assemble in your drawing code. These elements are fundamental to all drawing operations and are described in the following sections.
Cocoa provides its own data structures for manipulating basic geometric information such as points and rectangles. Cocoa defines the data types listed in Table 1-1. The member fields in each of these data structures are floating-point values.
For information on how to manipulate point, rectangle, and size data types, see Manipulating Geometric Types.
Cocoa provides support for drawing shape primitives with the
NSBezierPath class. You can use this class to create the following basic shapes, some of which are shown in Figure 1-2.
Bezier path objects store vector-based path information, making them compact and resolution independent. You can create paths with any of the simple shapes or combine the basic shapes together to create more complex paths. To render those shapes, you set the drawing attributes for the path and then stroke or fill it to “paint” the path to your view.
For more information about drawing shapes, see Paths.
Support for images is provided by the
NSImage class and its associated image representation classes (
NSImageRep and subclasses). The
NSImage class contains the basic interface for creating and managing image-related data. The image representation classes provide the infrastructure used by
NSImage to manipulate the underlying image data.
Images can be loaded from existing files or created on the fly. Figure 1-3 shows some bitmap images loaded from files.
Cocoa supports many different image formats, either directly or indirectly. Some of the formats Cocoa supports directly include the following:
Because they support many types of data, you should not think of image objects strictly as bitmaps. Image objects can also store path-based drawing commands found in EPS, PDF, and PICT files. They can render data provided to them by Core Image. They can interpolate image data as needed and render the image at different resolutions as needed.
For detailed information about Cocoa support for images and the ways to use images in your code, see Images.
In OS X v10.5 and later, you can use the
NSGradient class to create gradient fill patterns.
Cocoa provides an advanced text system for drawing everything from simple strings to formatted text flows. Figure 1-4 shows some basic examples of stylized text that you can create.
Because text layout and rendering using the Cocoa text system is a very complicated process, it is already well documented elsewhere and is not covered in great detail in this document. For basic information about drawing text and for links to more advanced text-related documents, see Text.
Views and Drawing
Nearly all drawing in Cocoa is done inside views. Views are objects that represent a visual portion of a window. Each view object is responsible for displaying some visual content and responding to user events in its visible area. A view may also be responsible for one or more subviews.
NSView class is the base class for all view-related objects. Cocoa defines several types of views for displaying standard content, including text views, split views, tab views, ruler views, and so on. Cocoa controls are also based on the
NSView class and implement interface elements such as buttons, scrollers, tables, and text fields.
In addition to the standard views and controls, you can also create your own custom views. You create custom views in cases where the behavior you are looking for is not provided by any of the standard views. Cocoa notifies your view that it needs to draw itself by sending your view a
drawRect: message. Your implementation of the
drawRect: method is where all of your drawing code goes.
By default, window updates occur only in response to user actions. This means that your view’s
drawRect: method is called only when something about your view has changed. For example, Cocoa calls the method when a scrolling action causes a previously hidden part of your view to be exposed. Cocoa also calls it in response to requests from your own code. If the information displayed by your custom view changes, you must tell Cocoa explicitly that you want the appropriate parts of your view updated. You do so by invalidating parts of your view’s visible area. Cocoa collects the invalidated regions together and generates appropriate
drawRect: messages to redraw the content.
Although there are numerous ways to draw, a basic
drawRect: method has the following structure:
// Draw your content
That's it! By the time your
drawRect: method is called, Cocoa has already locked the drawing focus on your view, saved the graphics state, adjusted the current transform matrix to your view's origin, and adjusted the clipping rectangle to your view's frame. All you have to do is draw your content.
In reality, your
drawRect: method is often much more complicated. Your own method might use several other objects and methods to handle the actual drawing. You also might need to save and restore the graphics state one or more times. Because this single method is used for all of your view's drawing, it also has to handle several different situations. For example, you might want to do more precise drawing during printing or use heavily optimized code during a live resizing operation. The options are numerous and covered in more detail in subsequent chapters.
For additional information about views and live resizing, see View Programming Guide. For more information about printing in Cocoa, see Customizing a View’s Drawing for Printing in Printing Programming Guide for Mac.
Common Drawing Tasks
Table 1-2 lists some of the common tasks related to drawing the content of your view and offers advice on how to accomplish those tasks.
How to accomplish
Update a custom view to reflect changed content.
Use Core Animation, set up a timer, or use the