Getting a View to Draw Itself
In this section, you will create a very simple view that will appear and paint itself green. It will look like Figure 17.4.
Figure 17.4 Completed Application
Create a new project of type Cocoa Application. Name the project ImageFun.
Using the File->New File menu item, create an Objective-C NSView subclass, and name it StretchView.
Create an Instance of a View Subclass
Open MainMenu.nib. Create an instance of your class by dragging out a CustomView placeholder from the Library (under Views & Cells -> Layout View) and dropping it on the window (Figure 17.5).
Figure 17.5 Drop a View on the Window
Resize the view to fill most of the window. Open the Info panel, and set the class of the view to be StretchView (Figure 17.6).
Figure 17.6 Set the Class of the View to StretchView
Size Inspector
Your StretchView object is a subview of the window's content view. This point raises an interesting question: What happens to the view when the superview resizes? A page in the Info panel allows you to specify that behavior. Open the Size Info panel, and set it as shown in Figure 17.7. Now it will grow and shrink as necessary to keep the distance from its edges to the edges of its superview constant.
Figure 17.7 Make the View Resize with the Window
If you wanted the view to stay the same height, you could let the distance between the bottom of the view and the bottom of the superview grow and shrink. You could also let the distance between the right edge of the view and the right edge of the window grow and shrink. In this exercise, you do not want this behavior. But if you did want the view to stick to the upper-left corner of the window, the Inspector would look like Figure 17.8.
Figure 17.8 Not This!
Figure 17.9 is a complete diagram of what the Size Inspector means.
Figure 17.9 What the Red Lines in the Size Inspector Mean
Save and close the nib file.
drawRect
When a view needs to draw itself, it is sent the message drawRect: with the rectangle that needs to be drawn or redrawn. The method is called automatically—you never need to call it directly. Instead, if you know that a view needs redrawing, you send the view the setNeedsDisplay: message:
[myView setNeedsDisplay:YES];
This message informs myView that it is "dirty." After the event has been handled, the view will be redrawn.
Before calling drawRect:, the system locks focus on the view. Each view has its own graphics context, which includes the view's coordinate system, its current color, its current font, and the clipping rectangle. When the focus is locked on a view, the view's graphics context is active. When the focus is unlocked, the graphics context is no longer active. Whenever you issue drawing commands, they will be executed in the current graphics context.
You can use NSBezierPath to draw lines, circles, curves, and rectangles. You can use NSImage to create composite images on the view. In this example, you will fill the entire view with a green rectangle.
Open StretchView.m and add the following code to the drawRect: method:
- (void)drawRect:(NSRect)rect { NSRect bounds = [self bounds]; [[NSColor greenColor] set]; [NSBezierPath fillRect:bounds]; }
As shown in Figure 17.10, NSRect is a struct with two members: origin, which is an NSPoint, and size, which is an NSSize.
Figure 17.10 NSRect, NSSize, and NSPoint
NSSize is a struct with two members: width and height (both floats).
NSPoint is a struct with two members: x and y (both floats).
For performance reasons, structs are used in a few places instead of Objective-C classes. For completeness, here is the list of all the Cocoa structs that you are likely to use: NSSize, NSPoint, NSRect, NSRange, NSDecimal, and NSAffineTransformStruct. NSRange is used to define subranges. NSDecimal describes numbers with very specific precision and rounding behavior. NSAffineTransformStruct describes linear transformations of graphics.
Note that your view knows its location as an NSRect called bounds. In this method, you fetched the bounds rectangle, set the current color to green, and filled the entire bounds rectangle with the current color.
The NSRect that is passed as an argument to the view is the region that is "dirty" and needs redrawing. It may be less than the entire view. If you are doing very time-consuming drawing, redrawing only the dirty rectangle may speed up your application considerably.
Note that setNeedsDisplay: will trigger the entire visible region of the view to be redrawn. If you wanted to be more precise about which part of the view needs redrawing, you would use setNeedsDisplayInRect: instead:
NSRect dirtyRect; dirtyRect = NSMakeRect(0, 0, 50, 50); [myView setNeedsDisplayInRect:dirtyRect];
Build and run your app. Try resizing the window.