All of the objects that you interact with on your computer screen are displayed within windows. This includes what we consider “normal” windows (those with titlebars and controls), as well as menu items, pop-up contextual menus, floating palettes, sheets, drawers, and the Dock.
Two interacting systems create and manage Cocoa windows. On one hand, Mac OS X’s window server creates a window and displays it on screen. The window server is a process that uses Quartz—the low-level drawing system—to draw, resize, hide, and move windows. As depicted in Figure 6-1, the window server also detects users events (such as mouse clicks or keyboard key presses) and forwards them to applications.
On the other hand, the window created by the window server is paired
with an object supplied by the AppKit—an instance of the
NSWindow
class. Each physical window in a Cocoa
program is managed by an instance of NSWindow
or a
subclass. As shown in Figure 6-2, when an
NSWindow
object is created, the window server
creates the physical window being managed. The window server
references the window by its window number and the
NSWindow
object instance by its own identifier.
Three classes explicitly define the functionality at the core of a
running application:
NSWindow
,
NSView
, and
NSApplication
. Each class plays a critical role in
drawing the user interface of the application and directing user
events to the various parts of a program. Each class inherits
functionality from the
NSResponder
and
NSObject
classes, as shown in Figure 6-3. The structure of their interaction is
sometimes called the "
core program
framework.”
NSResponder
NSResponder
is an abstract class that enables event
handling in all classes that inherit from it. It defines the set of
messages invoked when different mouse and keyboard events occur. It
also defines the mechanics of event processing among objects in an
application. We’ll cover events in more depth in
Chapter 8.
NSWindow
An NSWindow
object manages each physical window on
the screen. It draws the window’s frame area and
responds to user actions that close, move, resize, and otherwise
manipulate the window. The main purpose of an
NSWindow
is to display an
application’s user interface, or at least a part of
it, in its content area. The content area is that space below the
titlebar and within the window frame.
NSWindow
allows you to assign a custom object as
its delegate to participate in its activities. This allows you to add
application-specific window functionality to your application without
requiring knowledge of the NSWindow
class
internals.
NSView
Any object you see in a widow’s content area is an
instance of a subclass of the
NSView
class. Each
view owns a
rectangular region associated with a particular window. A view
produces the image content for that region and responds to events
occurring within it.
Graphically, a view can be regarded as a framed canvas. The frame locates the view in its superview, defines its size, and clips the drawing to its edges. The frame can be moved around resized, and rotated in the superview. Within the frame is the bounds of the view—the rectangle within which the view draws itself.
Views draw themselves as an indirect result of receiving the
display
message or one if its variants. This
message leads to the invocation of a view’s
drawRect:
method and the
drawRect:
methods of all subviews of that view.
The drawRect:
method should contain all the code
needed to redraw the view completely.
NSApplication
Every
application has
exactly one NSApplication
object instance to
supervise and coordinate the overall behavior of the application.
This object dispatches events to the appropriate windows, which, in
turn, distribute them onto their views. The application object
manages its windows; it also detects and handles changes in their
status, as well as its own active and inactive status. The
application object is represented in each application by the global
instance variable NSApp
.
Windows have numerous characteristics, the first of which being that they can be onscreen or offscreen. Onscreen windows are layered on the screen in tiers managed by the window server. Onscreen windows also can carry a status: key or main. Offscreen windows are hidden or minimized to the Dock and are not visible on the screen.
The key window responds to key presses for an application and is the primary recipient of messages from menus and dialog boxes. Usually a window is made key when the user clicks it. Each application can have only one key window at a time.
The main window is the principle focus of user actions for an application. Often user actions in a modal key window (typically a dialog box, such as the Font dialog or an Info window) have a direct effect on the main window. Main windows often have key status.
Cocoa applications usually include a Window menu in the menu bar at the top of the screen. The Window menu automatically lists the windows that have a titlebar, are resizable, and can become the main window. When a window’s title is changed, that new title is reflected in this menu. Figure 6-4 shows a Window menu in Project Builder, with two open windows.
A panel is a special kind of window that usually serves some auxiliary function in an application. For example, much of the functionality of Interface Builder, such as the view’s palette and inspector, is implemented using panels. To support the roles they typically play, panels differ from windows in the following ways:
To reduce screen clutter, an application’s panels—except for attention panels—are displayed only when the application is active. For example, when you have more than one application running, only the panels for the active application are in the foreground.
Panels can become the key window, but never the main window. For example, when working in Photoshop, you have a main window where you create and edit images. The other panels, such as the Layers panel, are open but not active (or key) until they are clicked; the focus then changes to that panel.
The user can close a panel that is the key window by pressing the Escape key (if the panel has a close button).
Inside of each window—inside the area enclosed by the titlebar and the other three sides of the frame—lies the content view . The content view is the root, or top, view in a hierarchy of views that belongs to the window. Like a tree, one or more views may branch from the content view. For example, each button, text field, and label in the Currency Converter application from Chapter 5 is a view located within the content view of the window, as illustrated in Figure 6-5. Enclosure determines the relationship between each view and its subviews.
The core program framework provides several ways for your application to access the participating objects, so you need not define outlets or instance variables for every object in the hierarchy.
By sending the appropriate message to the NSApp
global variable, you can obtain the application’s
NSWindow
objects.
You can get the content view of a window by sending it the
contentView
message. From the returned
NSView
object, you can get all subviews of the
view.
You can obtain from an NSView
instance most of the
objects that it references. For example, you can discover its window,
its superview, and its subviews.
The relationship between these parts of an application’s view hierarchy is shown in Figure 6-6.
Positioning of windows and views, as well as the correct propagation of events within them, requires the use of a set of coordinate systems. These systems then help locate objects in relation to the various parts of the onscreen display. The following three types of coordinate systems are used by Cocoa:
Screen coordinates
Window coordinates
View coordinates
The screen coordinate system is the basis for all other coordinate systems. Think of the entire screen as occupying the upper-right quadrant of a two-dimensional coordinate grid, as shown in Figure 6-7. The screen quadrant has its origin in the lower-left corner. The positive x-axis extends horizontally to the right, and the positive y-axis extends vertically upward. Each unit in the coordinate system represents an on-screen pixel.
Although Figure 6-7 represents the coordinate system using a single display device, the screen coordinate system is really a logical rectangular union of all the screen rectangles of all physical frame buffers attached to the computer. The origin lies at the lower-left corner of that unioned rectangle.
The screen coordinate system has just one function: to position windows on the screen. When your application creates a new window, it must specify the window’s initial size and location in screen coordinates.
The window coordinate system defines the coordinates used within a single window on screen. It differs from the screen coordinate system in only two ways:
It applies only to a particular window. Each window has its own coordinate system.
Its origin is at the lower-left corner of the window. If the window moves, the origin and the entire coordinate system move with it.
For drawing, each view uses a coordinate system transformed from the window coordinate system, or from its superview in the case of a contained view. This coordinate system has its origin point in the lower-left corner of the view, as shown in Figure 6-8, making it convenient for drawing operations.
This set of coordinate systems has several implications that are important in the layout and drawing of user-interface elements:
Subviews are positioned in the coordinates of their superview.
Each view’s coordinate system is a transformation of its superview’s system.
When a view is moved or transformed, all subviews are moved or transformed in concert.
Because a view has its own coordinate system for drawing, drawing instructions remain constant, regardless of any change in position of itself or its superview.
Controls are the user-interface objects that enable users to signal their intentions to an application and control what happens. Cells are rectangular areas embedded within a control. Each control can have one or more cells, allowing a single control to have multiple active areas. Figure 6-9 shows the relationship between controls and cells.
Controls and cells lie behind the appearance and behavior of most user-interface objects in Cocoa, including buttons, text fields, sliders, and browsers. Although they are quite different types of objects, they interact closely. Controls are responsible for the following:
Displaying the control to the user
Accepting user events, such as clicking or typing
Sending actions to other objects in response to a user event
A control usually delegates the first two responsibilities to cells.
Cells, which are subclass instances of the NSCell
class, let you display text or images in a view without the full
overhead of an NSView
subclass. This allows for
greater flexibility when creating a control, such as a spreadsheet
table, with many identical elements.
The controls that Cocoa provides fall into the categories listed in Table 6-1.
Control |
Description |
Boxes |
Group together other views, including controls, in an area that can have a border and title |
Browsers |
Display a list of data and allow the user to select items |
Buttons |
Send an action message to a target when clicked |
Combo Boxes |
Allow a user to enter a value either by entering it directly into a text field or choosing it from a pop-up list of preselected values |
Forms |
Group a related set of text fields |
Image Views |
Display a single image in a frame and, optionally, allows a user to drag an image to it |
Matrices |
Group cells that work together in various ways, such as radio buttons |
Outline Views |
Display hierarchical data to let the user expand or collapse rows |
Progress Indicators |
Show that a lengthy task is underway and, optionally, can display how much of that task is complete |
Sliders |
Display a range of values and have an indicator, or knob, indicating the current setting |
Steppers |
Increment or decrement a value, such as a date or time, that is displayed next to them |
Tab Views |
Group views on multiple pages together into one user-interface element |
Table Views |
Display a set of related records, with rows representing individual records and columns representing the attributes of those records |
Text Fields |
Display text that a user can select or edit |
Text Views |
Allow the editing of text |
Controls act as managers of their cells, telling them when and where to draw and notifying them when a user event occurs in their areas. This division of labor, given the relative “weight” of cells and controls, conserves memory and provides a great boost to application performance. For example, a matrix of buttons can be implemented as a single control with many cells instead of as a set of individual controls.
A control does not need a cell associated with it, but most
user-interface objects available in Cocoa are cell-control
combinations. Even a single button—from Interface Builder or
programmatically created—is a control (an
NSButton
instance) with an associated cell (a
NSButtonCell
instance).
The cells in a control such as a matrix must be the same size, but
they can be of different classes. More complex controls, such as
table views and browsers, can incorporate various sizes and types of
cells. Most controls that use a single cell, such as
NSButton
, provide convenience methods so you
don’t have to deal with the contained cell
directly.
When looking at the contents of cells, it is natural to consider only
text (NSString
) and images
(NSImage
). The content seems to be whatever is
displayed. However, cells can hold other kinds of objects, such as
dates (NSDate
), numbers
(NSNumber
), and even application-supplied custom
objects, which are shown in the user interface as strings.
One way to make your application’s user interface more attractive is to format the contents of fields that display currencies and other numeric data. Fields can have fixed decimal digits, limit numbers to specific ranges, have currency symbols, and show negative values in a special color.
Formatters
are objects that translate the values of certain objects to specific
on-screen representations. Formatters can also convert a formatted
string on a user interface into the represented object. For example,
Figure 6-10 shows how a date formatter translates
the contents of an NSDate
object into a specific
string for display.
You can create, set, and modify formatter objects programmatically or
with Interface Builder. Formatter objects handle the textual
representation of the objects associated with the cells and translate
what is typed into a cell to the underlying object. You can attach a
formatter object to a cell in Interface Builder or use the
setFormatter:
method of NSCell
to associate a formatter with a cell programmatically.
To show formatters in action, we’re going to create a simple application that shows the current date and time in a text field. In Project Builder, create a new Cocoa Application (File → New Project → Application → Cocoa Application) named “Simple Date”, and save it in your ~/LearningCocoa folder.
Begin by opening the application’s main nib file in Interface Builder:
In Project Builder’s Groups & Files pane, click on the disclosure triangle next to Resources to reveal the MainMenu.nib file.
Double-click on the nib file to open it in Interface Builder.
A default menu bar and window will appear when the nib file is opened.
Set the size and initial location of the application’s main window by resizing and moving the window in Interface Builder
Move the window near the upper-left corner of the screen by dragging its titlebar.
Make the window smaller using the resize control at the bottom-right corner of the window, as shown in Figure 6-11.
Now, add a text field object to the application’s window.
Select the Views palette by clicking the second button from the left in the toolbar of the Cocoa Views window, as shown in Figure 6-12.
Drag a text field object onto the window.
Resize the text field to make it wider, using the handles on the text field, as shown in Figure 6-13.
We’ll create a very simple
controller,
MyController
, which will be a subclass of
NSObject
. To define it:
Click the Classes tab of the MainMenu.nib window.
Select NSObject from the list of classes.
Press Return to create a new subclass of NSObject
,
and rename it MyController
.
Now the controller needs a way to send messages to the text field in the main window. Use Interface Builder to create an outlet for that purpose.
Select the MyController
class in the Classes
window.
Open the Show Info window (Tools → Show Info, or Shift-
-I), and select Attributes in the pull-down menu.
In the Outlets tab, click the Add button, and add an outlet named
textField
(as shown in Figure 6-14); enter the name, and press Return.
As the final step of defining the controller
in Interface Builder, create an instance of the
MyController
class.
Select MyController
in the Classes pane of the
MainMenu.nib window.
Choose instantiate from the Classes menu (Classes → Instantiate MyController, or Option-
-I).
When you instantiate a class (that is, create an instance of it), Interface Builder switches to the Instances pane and highlights the new instance, as shown in Figure 6-15. The instance is named after the class.
In fact, the instantiate command does not generate a true instance of
MyController
. It creates a proxy object used
within Interface Builder for defining connections to other objects in
the nib file. When the application is launched and the nib
file’s contents are loaded, the runtime system
creates a true instance of MyController
and uses
the proxy object to establish connections to other objects.
Now that you have created an instance of
MyController
, you can use it to declare a
connection between it and the text field you created earlier.
In the Instances panel of the MainMenu.nib
window, Control-drag a connection line from the
MyController
instance to the text field. When the
text field is outlined, as shown in Figure 6-16,
release the mouse button.
Interface Builder brings up the Connections pane of the Show Info window, as shown in Figure 6-17.
Select textField
, and click the Connect button.
Generate the source files so that we can add our controller code and run the application.
Go to the Classes tab of the MainMenu.nib file window.
Select the MyController
class.
Choose Create Files from the Classes menu (Classes → Create Files for MyController, or Option-
-F).
Interface Builder displays the dialog box shown in Figure 6-18.
Verify that the checkboxes in the Create column next to the .h and .m files are selected.
Verify that the checkbox next to Simple Date is selected in the Insert into targets column.
Click on the Choose button.
Save the nib file (File → Save, or
-S).
Now that we’ve built the basic interface, we can leave Interface Builder and switch to Project Builder to complete the application. Click on Project Builder’s icon in the Dock to leave Interface Builder.
By default,
outlet
declarations are dynamically typed using the
id
keyword. You
can use id
as the type for any object, meaning
that the class of the object is determined at runtime. When you
don’t need a dynamically typed object, you
can—and should—statically type it as a pointer to an
object. It takes a little extra time, but it is good programming
practice. Static typing also allows the compiler to
perform type checking, potentially saving you debugging time later.
When you look at the source code for MyController.h, note that generic outlets are declared as follows:
IBOutlet id variableName;
There are two ways to type outlets. The first is to indicate the type
in Interface Builder. Take another look at Figure 6-14, and notice the type pull-down as part of the
textField
outlet definition. You can use the
pull-down to select which type of object the outlet should be typed
as. The other way is to change the type in the header file. To do
this, use the following steps:
In Project Builder, select MyController.h in the Other Sources folder in the left pane.
Change the declaration in MyController.h to match the code shown in Example 6-1. Don’t forget to add the pointer star!
When an application is launched, the
NSApplicationMain
function loads the main nib file.
After a nib file has been completely unpacked and its objects
connected, the runtime system sends the
awakeFromNib
message to all objects derived from
information in the nib file, signaling that the loading process is
complete. All object’s outlets are guaranteed to be
initialized when
awakeFromNib
is called. This
lets objects in the nib file do any extra setup required before the
user or the rest of the application attempts to interact with them.
In this application, we’ll use the
awakeFromNib
method to print the current time to
the text field in the main window.
In the left pane, click on MyController.m in the Other Sources folder.
Edit the MyController.m file to match the code shown in Example 6-2.
Save the project (File → Save, or
-S).
Build and run the application (
-R). You should see a window that resembles Figure 6-19.
Quit the application.
Wait a minute . . . our date looks really nerdy. Instead of this representation for the date, we want to make a nicely formatted date. To do this, switch back to Interface Builder to perform the following steps:
Drag a date formatter from the Views palette to the text field, as shown in Figure 6-20.
While the text field is selected, bring up the Show Info window (Shift-
-I) if it isn’t open already.
In the Formatter pane of the Show Info window, specify the
%c
date format, as shown in Figure 6-21.
Save the nib file (File → Save, or
-S).
Return to Project Builder, and build and run (
-R) the project. You should see something like Figure 6-22.
Quit the application (Simple Date → Quit NewApplication, or
-Q).
The target/action pattern is part of the mechanism by which user-interface controls respond to user actions, enabling users to communicate their intentions to an application. The target/action pattern specifies a one-to-one relationship between two objects: the control (more specifically, the control’s cell) and its target. When a user clicks a user-interface control, it sends an action message to the target, as shown in Figure 6-23.
The target/action relationship is typically defined using Interface Builder, in which you select a target object for a control, along with a specific action message that will be sent to the target. Target/action relationships can also be set (or modified) while an application is running.
To show target/action pattern in practice, we are going to modify the Simple Date application we’ve already built.
In Interface Builder, open the MainMenu.nib file, and add a button named "Refresh” to the main window, using the following steps:
In the Cocoa-Views window, grab an NSButton
object, and drag it to the main window.
Change the name of the button by double-clicking on the “Button” name to highlight it, typing “Refresh”, then hitting Return to accept the new name. The interface should now look similar to Figure 6-24.
When the user presses the Refresh button, we want the date to update
itself. To do this, we need to define an
action
on our MyController
object that the button will
call. Define an action called refresh:
using the
following steps:
Select the MyController
class in the Classes pane
of the MainMenu.nib window.
In the Attributes pane of the Show Info window, click on the Actions tab and then on the Add button.
Change myAction:
to refresh:
and hit Return to add the action, as shown in Figure 6-25.
For MyController
to receive an action message from
the
button in the user interface, you must
connect the button to the controller. The button object keeps a
reference to its target using an outlet; not surprisingly, the outlet
is named target
. To make this connection:
Click on the Instances tab in the MainMenu.nib window.
Control-drag a connection from the Refresh button to the
MyController
instance in the
MainMenu.nib window, as shown in Figure 6-26. When the instance is outlined, release the
mouse button.
In the Connections pane of the Show Info window, make sure target is selected in the Outlets column.
Select refresh:
in the right column, as shown in
Figure 6-27.
Click the Connect button.
Save the MainMenu.nib file (File → Save, or
-S).
Since we made changes to the controller, the source files need to be updated so that we can add our controller code and run the application.
Go to the Classes tab of the MainMenu.nib file window.
Select the MyController
class.
Choose Classes → Create Files for MyController (Option-
-F). Follow the dialog boxes to save the files into the project.
Interface Builder will warn you that the file MyController.h already exists. Click on the Merge button to bring up the FileMerge tool, as shown in Figure 6-28. If you don’t see the window shown in Figure 6-28, look for the FileMerge icon on your Dock, and click it to bring the FileMerge window to the top.
The FileMerge tool consists of three panes. The left pane is the
newly generated file from Interface Builder, the right pane is the
file in your project, and the bottom pane is the result of the merge.
We want to keep our edits that were statically typed for the
textField
outlet. To do this, we select the #1
arrow, then “Choose right” from the
Actions pop-up at the bottom-right corner of the window.
Save the MyController.h file from FileMerge (File → Save Merge ,or
-S) and then close the window.
Return to Interface Builder. You will be prompted to merge the MyController.m file; do so.
Merge the Files (as shown in Figure 6-29) by
selecting “Choose both (left
first)” for the first block of code and
“Choose right” for the second block
of code. Unfortunately, at the time of writing this book, FileMerge
isn’t smart enough to handle this merge on its own.
If you encounter this problem, you’ll need to add
the curly braces after the refresh:
method
yourself.
Save the resulting merged file (File → Save Merge, or
-S), and quit the FileMerge tool (
-Q).
Save the nib file (File → Save, or
-S).
There are other ways of adding outlets and actions to your source code and the nib files that don’t involve using the FileMerge tool. We’ll see some of these other ways in later chapters.
Now switch back to Project Builder. Our next step is to edit the
MyController.m file and insert the code for the
refresh:
method, as shown in Example 6-3.
When the application launches, you can refresh the date display by pressing the Refresh button. Of course, the date won’t change if you’ve selected to show only the date in the text field. If you’ve opted to also display the current time, hitting the Refresh button should update the time.
Read the online documentation for the NSWindow
and
NSView
classes.
Give the window of our Simple Date application a title other than “Window”.
Go back to the Currency Converter application in Chapter 5, and statically type the
rateField
, dollarField
, and
totalField
outlets.