Learn Objective-C on the Mac - Mark Dalrymple [131]
The Basic Drawing Method, drawRect:
Now let’s get into the code. The SmileyView.m file created by Xcode contains an empty drawRect: method, which is where we’ll start writing our code. The drawRect: method is something you usually won’t call directly, except for cases where you’re implementing a subclass of an existing view, and calling [super drawRect:rect] to let the superclass do its part of the drawing. Instead, the drawRect: method is called automatically whenever the application’s main run loop determines that the view needs to be redrawn (typically after a view has been created or resized, or your own code has called setNeedsDisplay: on your view, passing YES as the argument). The drawRect: method takes one argument, a rectangle that indicates which portion of the view is considered “dirty” and needs to be redrawn. This can be used to optimize drawing of complex views, but in our examples we’ll just ignore that, and instead make use of the view’s bounds rectangle for drawing.
Graphics States
All drawing in Cocoa occurs within a particular context, which is represented by the NSGraphicsContext class. Depending on the context, we may be drawing directly into a window buffer, or drawing into a chunk of off-screen memory for later use. But apart from that, the context has some state information of its own that can change over time, such as the current color to be used for any drawing commands. When it’s time for your view to draw itself, it should do so in such a way that the graphics state is put back into the state it was in when drawing started.
Fortunately, NSGraphicsContext provides us with an easy way to do just that. Its saveGraphicsState method will push all relevant state info onto a stack, and the restoreGraphicsState method will pop the state back off the stack. You can use these two method calls to “bracket” your actual drawing code like the following excerpt shows, so you know you aren’t leaving the graphics context in an unexpected state.
Path Helpers
To draw the view shown at the beginning of this section, we’ll first draw the background, then the face itself. For each of those two elements, we’ll first fill in the background, then draw the edge. All of this is done using the NSBezierPath class. A Bezier path allows you to define paths of arbitrary complexity, including straight lines, points, curves, and so on. One of the niceties of the NSBezierPath class is that it gives you nice shortcuts for creating a Bezier path representing common shapes such as rectangles, ovals, and the like.
Let’s start by creating a path that defines the visible edge of the view, using a class method on NSBezierPath that gives us a rounded rect. The lines shown in bold below create a path, fill it with white, and then “stroke” the path (draw its edge) in black:
The first step uses the CGRectInset function to shrink our bounds rect a bit, giving us a little wiggle room so that we can draw a rounded rect with thick lines without having them clipped by our edges. Then we create a path representing a rounded rect, specifying the basic geometry of the rect as well as two numbers defining the size of the elliptical curves used for the rounded corners. After that, we issue simple commands to set colors, a line width, and do some drawing.
Colors and the Graphics Context
Note that specifying which color to use for a drawing operation is a separate step from the drawing operation itself, as is setting the line width. Also, although