Cocoa Diversions; More on Viewsby Michael Beam
Mac OS X gave us a host of interface enhancements, for better or for worse. Some are seemingly frivolous, such as the whiz-bang animations that dominate Aqua, and others haven proven truly useful, such as the Finder’s column view. Personally, I dig it all.
In today’s column we’re going to look at two of these Aqua enhancements—-one seemingly frivolous and the other not. One of them--animated window resizing--can be readily experienced within System Preferences: just choose a preference pane and you’ll see the System Preferences window do an animated resize while the selected preference pane loads.
The other Aqua interface element we’re going to look at can be seen in applications such as OmniWeb and Mail. In OmniWeb you see it whenever you open your bookmarks or history, and Mail uses it to keep your mailboxes tucked away when you don’t need them, but always close at hand when you do. No, “it” is not an overhyped and seemingly underwhelming technology (although it might be if I don’t get to my point). “It” is drawers. In addition to learning about animated window resizing, we’ll also delve into the wonderful world of drawers.
Here is how OmniWeb uses drawers to hold bookmarks.
You may be asking yourself what these two things have in common for the humble programmer. After all, they’re pretty distinct UI elements, so why cover them in the same column? The answer lies in how they’re implemented in Interface Builder, specifically a neat trick you can use to make user interfaces independent of a window. Before I get ahead of myself, let's make ourselves a project.
Create a new Cocoa Application project and, for lack of a better name, call it Aqua Stuff. Then open MainMenu.nib in Interface Builder, and we’ll start things out with the drawer half of the column.
Adding a Drawer
Adding a drawer to a window is easy in Interface Builder. The Cocoa-Windows palette is the storehouse for drawers. First drag the drawer object onto the Instances tab in your nib window. For now disregard the window with the preattached drawer in the top-right of the palette-—I’ll talk about that after we do things the long way.
So what do we do with a drawer now that it's an object in our nib file? Let's take a look at some of the characteristics of this drawer, particularly what outlets are declared.
Selecting the NSDrawer instance and bringing up its Connections info panel (Command-2) reveals that an instance of NSDrawer initially has three outlets; they are named contentView, delegate, and parentWindow. We’re interested in the first and last of these three.
The parentWindow outlet simply connects to the window we want to have the drawer attached to. In our case, we’ll use the window that came with MainMenu.nib. Drag a connection wire from the drawer icon to the window icon and make the connection to parentWindow.
The second outlet we’re concerned with, contentView, is a link to the NSView that contains the user interface to be displayed in the drawer. To make this view we’re going to use the same NSView container that we used for drawing, which, if you recall, is found in the Cocoa-Containers palette. However, rather than dragging this view onto the main window like we did to set up a drawing view, we’re going to drop it on the Instances tab in the same way we did the drawer. Do this now and you will see the view open within a window.
This does not mean that the view is a part of the window in the actual interface, that’s just how we work with it in Interface Builder. The thick grey border around the view in the window indicates that the view is independent of the window. In fact, the view is not a subview of any other window or view; it is all alone. We’re going to change that, however, by setting this view to be the content view of the drawer. In the same way we connected the window to the drawer, connect the NSView object to the contentView outlet of the drawer.
With the NSView we can now go about making the user interface that we want tucked away in the drawer. The Aqua Human Interface Guidelines (http://developer.apple.com/techpubs/macosx/Essentials/AquaHIGuidelines/index.html) states that drawers should contain often-used controls that needn’t be visible at all times. In the OmniWeb screenshot above we see that the folks at Omni have put some variant of an outline view in the bookmarks drawers. You can put anything in a drawer content view that you can put in a window. There are some issues to consider when building your drawer interface. Primarily, there are size constraints to think about with a drawer.
|OK, so maybe animated window resizing isn't that frivolous, but what about using drawers? Questions and comments here.|
Before we discuss how drawers are sized, let's take a look at the attributes of a drawer. You’ll see that the only attribute that we can change is the preferred edge of the parent window that the drawer will be attached to. The astute reader will notice the use of the word "preferred." What this means is that, by default, the drawer will be attached to the indicated edge of the window.
However, if when opening the drawer there is no room for it between the preferred edge of the window and the adjacent edge of the screen, the drawer will slide out from the opposite side of the window. Smart, huh? For the purposes of this discussion let's leave the preferred edge set to left and assume that drawers will be attached to the left or right side of the window.
Now let’s talk about how drawers are sized. If you open the Size Info panel for the drawer (Command-3) you will see the various size settings we have at our disposal. At the top we can set the width of the content size, but not the height. Rather, the height is calculated (if the preferred edge is top or bottom, the reverse will be true: you can set the height, but the width is calculated). The width should match the width of your own content view. The height (or width) of the drawer is calculated from the height of the window minus the leading offset and the trailing offset.
"But what exactly are these offsets?" you ask. Excellent question!