Repositioning or resizing a view is a potentially complex operation. When a view moves or resizes it can expose portions of its superview that weren’t previously visible, requiring the superview to redisplay. Resizing can also affect the layout of the view’s subviews. Changes to a view's layout in either case may be of interest to other objects, which might need to be notified of the change. The following sections explore each of these areas.
Moving and Resizing Views Programmatically
Autoresizing of Subviews
Notifications
After a view instance has been created, you can move it programmatically using any of the frame-setting methods: setFrame:, setFrameOrigin:, and setFrameSize:. If the bounds rectangle of the view has not been explicitly set using one of the setBounds... methods, the view's bounds rectangle is automatically updated to match the new frame size.
When you change the frame rectangle, the position and size of subviews' frame rectangles often need to be altered as well. If the repositioned view returns YES for autoresizesSubviews, its subviews are automatically resized as described in “Autoresizing of Subviews.” Otherwise, it is the application's responsibility to reposition and resize the subviews manually.
None of the methods that alter a view's frame rectangle automatically redisplay the view or marks it as needing display. When using the setFrame... methods, you must mark both the view being repositioned and its superview as needing display as in the code fragment shown in Listing 3-1.
Listing 3-1 Marking view contents for display after modifying the frame
NSView *theView; /* Assume this exists. */ |
NSRect newFrame; /* Assume this exists. */ |
[[theView superview] setNeedsDisplayInRect:[theView frame]]; |
[theView setFrame:newFrame]; |
[theView setNeedsDisplay:YES]; |
This code fragment marks the superview as needing display in the frame of the view about to be moved. Then, after the new frame rectangle of theView is set, the altered view is marked as needing display in its entirety, which is nearly always the case. The setBounds... methods also don’t redisplay the receiving view, but because their changes don’t affect superviews, you can simply mark the receiving view instance as needing display.
NSView provides a mechanism for automatically moving and resizing subviews in response to their superview being moved or resized. In many cases simply configuring the autoresizing mask for a view provides the appropriate behavior for an application. Autoresizing is on by default for views created programmatically, but you can turn it off using the setAutoresizesSubviews: method.
Interface Builder allows you to set a view's autoresizing mask graphically with its Size inspector, and in test mode you can immediately examine the effects of autoresizing. The autoresizing mask can also be set programmatically.
A view's autoresizing mask is specified by combining the autoresizing mask constants using the bitwise OR operator and sending the view a setAutoresizingMask: message, passing the mask as the parameter. Table 3-1 shows each mask constant and how it effects the view's resizing behavior.
Autoresizing Mask | Description |
|---|---|
| If set, the view's height changes proportionally to the change in the superview's height. Otherwise, the view's height does not change relative to the superview's height. |
| If set, the view's width changes proportionally to the change in the superview's width. Otherwise, the view's width does not change relative to the superview's width. |
| If set, the view's left edge is repositioned proportionally to the change in the superview's width. Otherwise, the view's left edge remains in the same position relative to the superview's left edge. |
| If set, the view's right edge is repositioned proportionally to the change in the superview's width. Otherwise, the view's right edge remains in the same position relative to the superview. |
| If set and the superview is not flipped, the view's top edge is repositioned proportionally to the change in the superview's height. Otherwise, the view's top edge remains in the same position relative to the superview. If set and the superview is flipped, the view's bottom edge is repositioned proportionally to the change in the superview's height. Otherwise, the view's bottom edge remains in the same position relative to the superview. |
| If set and the superview is not flipped, the view's bottom edge is repositioned proportional to the change in the superview's height. Otherwise, the view's bottom edge remains in the same position relative to the superview. If set and the superview is flipped, the view's top edge is repositioned proportional to the change in the superview's height. Otherwise, the view's top edge remains in the same position relative to the superview. |
For example, to keep a view in the lower-left corner of its superview, you specify NSViewMaxXMargin | NSViewMaxYMargin. When more than one aspect along an axis is made flexible, the resize amount is distributed evenly among them. Figure 3-3 provides a graphical representation of the position of the constant values in both normal and flipped superviews.
When one of these constants is omitted, the view's layout is fixed in that aspect; when a constant is included in the mask the view's layout is flexible in that aspect. Including a constant in the mask is the same as configuring that autoresizing aspect with a spring in Interface Builder.
Note: If a view is created in Interface Builder and no autoresizing flags are set in the view's inspector, then setAutoresizesSubviews: is automatically set to NO. Before programmatically modifying the autoresizing mask, you need to explicitly enable autoresizing for the superview by sending the superview a setAutoresizesSubviews: message, passing YES as the parameter.
When you turn off a view's autoresizing, all of its descendants are likewise shielded from changes in the superview. Changes to subviews, however, can still percolate downward. Similarly, if a subview has no autoresize mask, it won’t change in size, and therefore none of its subviews autoresize.
A subclass can override resizeSubviewsWithOldSize: or resizeWithOldSuperviewSize: to customize the autoresizing behavior for a view. A view's resizeSubviewsWithOldSize: method is invoked automatically by a view whenever its frame size changes. This method then simply sends a resizeWithOldSuperviewSize: message to each subview. Each subview compares the old frame size to the new size and adjusts its position and size according to its autoresize mask.
Important: Several cautions apply to autoresizing. For autoresizing to work correctly, the subview being autoresized must lie completely within its superview’s frame. Autoresizing doesn’t work at all in views that have been rotated. Subviews that have been rotated can autoresize within a nonaltered superview, but then their descendants aren’t autoresized.
Beyond resizing its subviews, by default an NSView instance broadcasts notifications to interested observers any time its bounds or frame rectangles change. The notification names are NSViewFrameDidChangeNotification and NSViewBoundsDidChangeNotification, respectively.
An NSView instance that bases its own display on the layout of its subviews should register itself as an observer for those subviews and update itself any time they’re moved or resized. Both NSScrollView and NSClipView instances cooperate in this manner to adjust the scroll view's scrollers.
By default both frame and bounds rectangle changes are sent for a view instance. You can prevent an NSView instance from providing the notifications using setPostsFrameChangedNotifications: and setPostsBoundsChangedNotifications: and passing NO as the parameter. If your application does complicated view layout, turning change notifications off before layout and then restoring them upon completion may provide a performance improvement. As with all performance tuning, it is best to first sample your application to determine if the change notifications are having a negative impact on performance.
Last updated: 2008-04-10