| < Previous PageNext Page > |
An NSView object (or simply, a view) occupies a rectangular area in a window. In Cocoa, views are instances of a subclass of NSView. They are the most pervasive type of object in the Application Kit; nearly every object you see in a Cocoa application is a view. Views are in the front line of both drawing and event handling, and hence are one of the more important types of objects to understand.
You can think of the drawing a view performs on the screen as a visual representation of the object itself. In a very real sense, a view draws itself. It also provides a surface that can be responsive to input from a mouse, keyboard, or other input device.
Further Reading: See Cocoa Drawing Guide for a comprehensive description of concepts and tasks related to drawing with NSView objects. Also, View Programming Guide for Cocoa describes various tasks related to the manipulation of views.
Varieties of Views
The View Hierarchy
View Geometry and Coordinates
How Views Get Drawn
Views and Printing
Views and Events
NSView is a class that defines the basic drawing, event-handling, and printing architecture of an application. NSView itself does not draw content or respond to user events, so you typically don’t interact with a direct instance of NSView. Instead you use an instance of a custom NSView subclass. A custom view class inherits from NSView and overrides many of its methods, which are invoked automatically by the Application Kit.
If you look at the class hierarchy of the Application Kit (Figure 1-9), you'll notice a proliferation of classes inheriting, directly or indirectly, from NSView. These classes fall into several categories:
Controls. Controls are views that users manipulate (for example, by clicking or dragging) to indicate a choice to an application. Buttons, sliders, text fields, and steppers are examples of controls. Controls usually (but not always) work in concert with cell objects, which do not inherit from NSView. “Controls and Menus” discusses controls at more length.
Container views. Some views are used to enclose and present other views or more primitive data. They may allow the editing of data or a more efficient presentation of a user interface. Among these kinds of views are NSTextView, NSImageView, NSBox, NSSplitView, and NSTabView objects.
Compound views. Some views are composed of other views. When you see a text view in a Cocoa application, it includes not just an NSTextView object but also an NSClipView object and an NSScrollView object (which itself includes NSScroller objects). Another example are table views (instances of NSTableView), which have constituent objects for table headers and table columns (the latter of which is not a view).
Wrapper views. A few views act as a Cocoa "host" for a Mac OS X technology. Examples of these objects are instances of NSOpenGLView and NSMovieView.
There is some overlap between some objects in these categories. For example, an NSTableView object is a compound object, but it is also a control.
As you may recall from the earlier discussion of windows (“Windows”), each view object is associated with the window in which it is displayed. All of the views in a window are linked together in a view hierarchy. Each view has another view as its superview and may be the superview for any number of subviews. At the top of the hierarchy of views is the window's content view, which has no public superview (it does have a private one). The key visual characteristic of the view hierarchy is enclosure: a superview encloses its subviews, which are positioned relative to it. Figure 6-11 illustrates this enclosure.
Arranging views in a hierarchy is beneficial for both drawing and event handling. It benefits drawing in three ways:
It permits a complex view to be constructed out of other views. For example, a graphical keypad might be a container view with a separate subview for each key.
It also permits each view to have its own coordinate system for convenient drawing. Views are positioned within the coordinates of their superviews, so when a view object is moved or its coordinate system is transformed, all of its subviews are moved or transformed with it. Because a view draws within its own coordinate system, its drawing instructions can remain constant no matter where it is or where its superview moves to on the screen.
It is used to set the layering order of views rendered in a drawing pass (see “Displaying a View”).
Note: Don't confuse the view instance hierarchy with the view class inheritance hierarchy. The view inheritance hierarchy is the arrangement of classes based on shared attributes, interface, and behavior. A view instance hierarchy is an arrangement of particular view instances based on enclosure.
The view hierarchy (referring now to the view instance hierarchy) plays an important role in event handling because it is a major part of the responder chain. See “Responders and the Responder Chain” for more about the responder chain.
The view hierarchy is dynamic: As an application runs, you can rearrange views, add them, and remove them. You can move a view from one window to another, and you can move a view around a particular hierarchy.
NSView has three relationship properties that help to locate a view in the view hierarchy:
window—the window (NSWindow object) in which the view appears
superview—the view that's immediately above the view in the hierarchy
subviews—the list of views that contained by the view (the list could have zero or more views)
Reflecting these properties, Figure Figure 6-12 diagrams the relationships of a window object and its view hierarchy.
View geometry is largely defined by two rectangles associated with each view: its frame and its bounds. Although these rectangles circumscribe the same area, they have different purposes. Together they help to define a view's position and size, and the coordinate system in which it draws and responds to events.
Note: Dimensions and locations described with these rectangles are expressed in floating-point values.
The frame rectangle defines the area of a view, the tablet on which it can draw. If you think of a view as a rectangular area on a window, the frame specifies the dimensions of this rectangle and its location in the window. A view can draw only within its frame; by default, the Application Kit enforces clipping of the content the view draws to the view's frame.
As shown in Figure 6-13, the frame rectangle of a view usually lies within the frame rectangle of its superview. But it doesn't have to. A view's frame rectangle can extend outside its superview's frame, but its drawing is clipped to its chain of containing ancestor views. The only part of the view that's visible on the screen is that part within the view's frame rectangle and within the frame rectangles of all its ancestor views.
Figure 6-14 also shows three hierarchically related views. In this case, however, the middle view lies partially outside it superview's frame rectangle. Although the lowest view lies entirely inside its superview, it also lies partially outside an ancestor view, so only the colored portion of it is visible.
Some views may contain more material than a window has room enough to display—a view containing the contents of a long document, for example. Such a view may be made the subview of another, smaller view that exposes only part of it. This smaller view is known as a clip view (an instance of NSClipView). With the aid of enclosing scroll view (NSScrollView) and the scrollers (NSScroller) that it manages, users can control the visible portion of the document within its clip view. As the subview moves, different portions of it are scrolled into view.
You move the location of a view within its subview by resetting its frame's origin. You change the size of a view by altering the size of the frame rectangle. Because these values are interpreted according to the superview's coordinate system, the size and location of a view on a screen can also change if the superview's coordinates change.
You can rotate a view around its frame origin. Rotation doesn't affect the shape or size of the view; it remains a rectangle even though it has been turned at an angle and the sides of its frame are no longer parallel to its superview's x-axis and y-axis. The frame's origin stays at the same point regardless of the angle of the frame's rotation. Subviews of a rotated view remain aligned with it and hence are also turned in relation to the superview of the rotated view. Figure 6-15 illustrates the same three views shown in Figure 6-13, but here the view in the center of the hierarchy has been rotated.
While a view's frame provides the means to size and position the view within its containing superview, it is of little use to the view for drawing. A view performs all drawing and event handling in its own local coordinate system, which is defined by its bounds rectangle.
A view's bounds rectangle defines the way that its local coordinate system is mapped onto the view's area. It describes exactly the same physical area as the frame rectangle, though this area is stated in the view's local coordinate system. By default, a view's bounds rectangle is identical in size to its frame, and has an origin of (0.0, 0.0). Under this arrangement, the view positions and draws its content using positive-valued coordinates.
However, If the view is flipped, this situation changes. A view can flip its coordinate system so that the drawing origin is in the upper-left corner of the view and the positive y-axis extends downward. Figure 6-16 shows what a flipped coordinate system looks like. Flipped views are especially useful for applications that draw text in languages such as English, where text starts in the upper-left corner of a document and flows right and then down.
Views typically use the bounds rectangle to ensure that they don't fruitlessly attempt to draw something that will never be rendered on the screen. Because drawing is clipped if it falls outside of any ancestor view, the bounds rectangle by itself is a reliable guide only for views that aren't scrolled and stay entirely within the frame rectangles of all their ancestors. The NSView class provides other programmatic ways to determine where to draw, but the bounds rectangle remains part of any drawing calculation.
The bounds rectangle provides a view with its drawing coordinates. Before a view draws itself, its coordinate system is made the current coordinate system for the application (see “How Views Get Drawn” for details). Recall that the default coordinate system for a view is the same as its superview's, except for these differences:
The point that locates the view's frame in its superview is made the origin (0.0, 0.0) of the bounds rectangle, and hence is the origin of the drawing coordinates.
If a view's frame is rotated, its drawing coordinate system is rotated with it; the bound's x-axis and y-axis remain parallel with the frame's axes.
If a transformation is performed on the bounds, however, these details can change.
Figure 6-17 illustrates the relationship between a view's default coordinate system and the coordinate system of its superview. The innerView object in this diagram is located at (400.0, 200.0) in its superview coordinate system. For any drawing that innerView does, this same point is treated as the coordinate origin (0.0, 0.0). When innerView draws text beginning at (100.0, 200.0), as it does in this example, the point is measured from its own origin, not its superview's. Even if newView is rotated, as shown in the diagram, the bound's axes rotate with the frame; the origin of the view's drawing coordinates remain unchanged.
A view can modify its default local coordinate system in several ways:
It can translate its origin to a point other than that defined by the frame's origin.
It can scale the size of units along its bounds axes to a value different than its superview's.
It can rotate the axes of its bounds rectangle around the bounds origin so that they're no longer aligned with the frame's axes.
These modifications alter the coordinate system that the view draws in, and may affect the appearance of what's drawn, but they don't change the area where the drawing appears. In other words, they don't affect the view's frame rectangle.
Views are the primary objects responsible for drawing window content in a Cocoa application. Other Application Kit objects can draw (such as NSCell, NSBezierPath, and NSAttributedString objects), but they require an NSView "host" object to provide the drawing surface and coordinate the drawing. The following sections give a high-level overview of how the Application Kit coordinates the drawing of a view.
A view typically doesn't draw itself whenever it wishes. It must be explicitly marked as invalid (or "dirty") and thus in need of display. Redrawing of the view can then either take place immediately or, if the NSWindow auto-display feature is turned on, it can be deferred until the conclusion of the current cycle of the main event loop. Windows have auto-display enabled by default. (Auto-display is described in “The Main Event Loop” and in “Windows and Drawing”.)
You can initiate immediate redrawing of a view or a portion of a view with one of the NSView display methods (so-called because each of these methods has "display" embedded in its name). These methods differ in various respects but they all result in the Application Kit doing the following things:
Locking focus on the invalidated view (described in “Locking Focus”)
Invoking the view's drawRect: method (described in “What Happens in drawRect:”)
At the conclusion of a drawing pass, flushing the window associated with the view (if the window's backing store is double-buffered)
Rather than displaying views immediately, the recommended course for most situations is to use the auto-display mechanism and during an event cycle mark views (or parts of views) as needing display. The NSWindow associated with the views collects the marked views in a list ordered by position in the view hierarchy, topmost view first. At the end of the event cycle, it recursively processes this list in one drawing pass, locking focus on each view in turn and asking the view to draw itself entirely or the portion of the view that was marked as needing display. When all views have been drawn, the window (if buffered) is flushed.
The Application Kit may request drawing of additional views and view areas beyond those that your application explicitly marks as needing redisplay. It may determine that additional view drawing is needed to fully update the corresponding window area. That's because an important aspect of the drawing of views is view opacity. A view does not have to draw every bit of its surface, but if it does it should declare itself to be opaque (by implementing isOpaque to return YES). When you mark a view for display, the Application Kit checks the opacity of the view, and if it is not opaque (that is, partially transparent) the Application Kit goes up the view hierarchy until it finds a superview that is opaque. It calculates the portion of the opaque ancestor covered by the original view. Then it draws forward through the hierarchy from this view to the view originally marked as dirty. If you want the Application Kit to not look for the first opaque ancestor before drawing a view, there are several "display ignoring opacity" methods you can use (which are listed in Table 6-1 and Table 6-3).
You can also mark portions of views and their subviews as needing display and then have them redrawn at once, instead of waiting for the auto-display mechanism to trigger. The NSView display methods that offer this feature all begin with displayIfNeeded.... Even though display is immediate, these methods are more efficient than sending display or displayRect: messages to isolated views.
Table 6-1 shows the NSView display methods for displaying individual views or regions of views immediately.
Display area and opacity | Display method |
|---|---|
Entire view | |
Partial view | |
Entire view, ignoring opacity | None |
Partial view, ignoring opacity |
Table 6-2 lists the methods that mark views or regions of views for redrawing using the auto-display feature.
Display area | Display method |
|---|---|
Entire view | |
Partial view |
Table 6-3 lists the methods that force an immediate display of views (or portions of views) invalidated with the methods in Table 6-2.
Display area and opacity | Display method |
|---|---|
Entire view | |
Partial view | |
Entire view, ignoring opacity | |
Partial view, ignoring opacity |
To reiterate, displaying views immediately is less efficient than using the auto-display feature, and immediately displaying only the marked parts of views falls somewhere in between. In addition, it is generally more efficient to mark areas of views as dirty rather than entire views. The display methods are a convenience for application development. A view can lock focus, draw itself, and unlock focus. But this is recommended only in certain circumstances, such as when animating content on a timer callback. In general, you should not try to circumvent the Application Kit's display mechanism.
When the Application Kit—or your code—locks focus on a view, NSView completes the following set-up steps:
It transforms the view's coordinate system from its superview's coordinate system and makes it the application's current coordinate system.
It constructs a clipping path to define the rectangular area outside of which the view cannot draw.
It activates other parameters of the current graphics state to establish the view's drawing environment.
You lock focus on a view by sending it a lockFocus (or related lockFocus...) message. After drawing is completed in a focused view, you unlock focus by sending it unlockFocus. The Application Kit automatically locks and unlocks focus when sending a view a drawRect: message.
A focused view is one that is currently drawing. Every window has a window graphics context, which defines a window server destination for drawing. This graphics context contains one or more graphics states, each of which isolates drawing operations for a particular view. The current graphics state contains the effective coordinate system and other drawing parameters for the most recently focused view.
A graphics state includes the current transformation matrix, which is a mathematical construct used to map points from one coordinate space to another through rotation, scaling, and translation operations. A graphics state also defines the current clipping area and other parameters that drawing operations may consult when they render an image. These parameters include the following:
The current color for fill and stroke operations
Alpha value (transparency)
Line attributes, including width, join, cap, dash, and miter limit
Anti-aliasing value
Current color space
Text attributes: font, font size, character spacing
Blend mode
It's possible to lock focus on a view when another view already has focus. In fact, this is what the Application Kit does during a drawing pass. Because each view keeps track of its own coordinate system as a modification of its superview's, a view is generally brought into focus after its superview. In a group of invalidated views, the Application Kit locks focus on the topmost view and works its way down the view hierarchy, making a nested series of lockFocus calls. As focus is locked on each successive view, the current graphics state is saved on a stack maintained by the graphics context (see Figure 6-18). As focus is unlocked on a view, the graphics state at the top of the stack is "popped" and restored as the current graphics state.
After the Application Kit locks focus on a view, it sends it a drawRect: message. A view's class implements this method to draw the view. A rectangle is passed in to drawRect: that defines the area to be drawn in the view's coordinate system. This rectangle might or might not correspond to the bounds of the view. It could be the union of all rectangular regions of the view that have been marked as invalid, or it could be a superset of that.
The sending of drawing instructions and data to the window server has a cost that should be avoided when possible, especially for drawing that will not end up being visible. A major Application Kit optimization is restricting the area to draw in, especially when the drawing is complex. The view can choose to draw itself entirely—the least efficient of alternatives—or it can draw the area defined by the passed-in rectangle. A potentially even more efficient procedure for a view would be to obtain the list of invalidated regions (through the NSView method getRectsBeingDrawn:count:) and selectively draw each of these regions in turn.
In its implementation of drawRect: the view class invokes various functions and methods to specify the drawing instructions and data to be passed to the window server. The Application Kit provides the following high-level drawing functions and methods:
Drawing functions (declared in NSGraphics.h) to draw, fill, erase, and perform other operations with rectangles
Methods to construct lines and shapes with bezier paths (NSBezierPath class)
Methods to create and apply affine transforms, involving translation, scaling, and rotation operations (NSAffineTransform class)
Color and color-space methods (NSColor and NSColorSpace)
Methods for creating and compositing images (NSImage and various image-representation classes)
Methods for drawing text (NSString and NSAttributedString)
The Application Kit implements these methods and functions using Core Graphics (Quartz) functions and types. A view can also use these Core Graphics functions natively when it draws itself. These Quartz client library functions map directly to rendering operators of the window server, resulting in a raster (bitmap) image that, for in-window drawing, becomes part of the window's backing store.
The drawing of views does not have to occur on the main thread; each thread of an application has the ability to lock focus on a view and draw. However, there are the following qualifications:
Mutations of the properties of an NSView object (for example, its frame rectangle) should happen only on the main thread.
When an NSView display method is invoked, the Application Kit grabs a lock for drawing in the receiving view's window; you cannot perform any custom drawing until the display method returns. This means that only one thread can draw into a given window at a time.
Views are the foundation of the Cocoa printing architecture. They provide the content to be printed, just as they provide content to be displayed on a screen. The general procedure is the same: The Application Kit locks focus on the view, its drawRect: method is invoked, the view draws the printable content, and focus is unlocked. You can tell a view to print itself by invoking its print: method.
Views are the objects in an application that respond directly to most user events such as mouse clicks or key presses. They are almost always the object providing the surface on which a user event occurs. Consequently, these front-line objects are given the first shot at handling event messages.
As discussed in “Windows and Event Handling”, a window forwards a user event (as an event message) to the view in its view hierarchy that should receive the event. For mouse events, it sends the message to the view under which the event occurred. If the event is a key event, the window sends the message to its first responder, which is usually the view with key focus. In order to receive the key event, the view must declare that it accepts first-responder status (that is, it overrides the NSResponder method acceptsFirstResponder to return YES).
Views are responder objects, which means they inherit the programmatic interface of the NSResponder class. Event messages invoke methods declared by this class; examples of such messages are mouseDown:, mouseMoved:, and keyUp:. To handle an event, a view's class must implement the appropriate NSResponder method; in its implementation it can inspect the passed-in NSEvent objects (which encapsulates information about the event) and proceed from there. If a view does not handle an event, the next responder up the responder chain—which is usually a superview—is given an opportunity to handle the event. “Responders and the Responder Chain” describes responders and how events travel up the responder chain.
Views also play a major role with action messages. These messages often originate as event messages sent to NSControl objects; in handling them, a control object sends an action message to a target object. If no target is specified, the application searches up the responder chain for an object that can respond to the action message. For more on NSControl objects and the NSCell objects they use, see “Controls and Menus”.
| < Previous PageNext Page > |
© 2006 Apple Computer, Inc. All Rights Reserved. (Last updated: 2006-12-20)
|