When a Cocoa-based application starts up, it loads all of the objects in the main nib file, then initializes, connects, and displays them. This can take some time, and the more objects you have in your interface, the longer it will take. While this process is happening, nothing else can happen in your application, and the user gets to watch the application icon bounce.
To shorten load time, lower initial memory consumption, and help organize your application better, Cocoa lets you use multiple nib files and load them on demand. For example, you can have separate nib files for info panels, toolbars, and dialog boxes. This chapter shows you how to use auxiliary windows with your application, explaining how to load them and how to manipulate their contents to create inspectors.
So far, we haven’t paid much attention to the special File’s Owner object that shows up in Interface Builder—other than to say that it is a proxy object that “owns” the objects in a nib file. However, when working with multiple nib files, it is crucial to understand the role that the File’s Owner plays as the object that loads—and thus “owns”—the nib file. When working with a single nib file, it is easy enough to create connections between your code and controls in the nib file. However, when working with multiple nib files, it becomes more difficult to make clear connections between controls in an auxiliary nib file and those of the main application. To make these connections, you use the File’s Owner object proxy.
In the applications that we’ve put together so far,
File’s Owner has been assigned to the main object
(an instance of NSApplication
) of the application
or, in the case of a document-based application, a document object (a
subclass of NSDocument
). You can see this
connection in Interface Builder by selecting the
File’s Owner and looking at the
Attribute’s inspector, as shown in Figure 16-1.
As you can see from Figure 16-1, you can assign the File’s Owner object to any class. When creating an auxiliary nib file, you will need to assign the File’s Owner proxy to the class that will load the nib and be responsible for mediating between the functionality of the secondary window and the rest of the application.
To illustrate how to use auxiliary windows and how the File’s Owner proxy enables the various parts of an application to communicate, we will create a simple inspector to tell us how many characters are in a text view.
Create a new Cocoa Application project in Project Builder (File → New Project → Application → Cocoa Application) named “Simple Inspector”, and save it in your ~/LearningCocoa folder.
Open the MainMenu.nib file in Interface Builder.
Create a subclass of NSObject
in Interface
Builder, named Controller
. (Click on the Classes
tab of the MainMenu.nib window, find NSObject
,
Control-click it, and select Subclass NSObject).
Create an action method on the Controller class, named
showInfoPanel:
.
Create four outlets on the Controller class, named
infoPanel
, infoPanelController
,
textLengthField
, and textView
,
and assign their types as shown in Table 16-1 and
Figure 16-2.
The infoPanel
outlet will hold a reference to our
inspector panel. The infoPanelController
will
serve as the window controller for the panel. The
textFieldLength
outlet will point to a text field
in our inspector panel that will display the total number of
characters in the textView
.
Generate the source-code files for the Controller
class (Classes → Create Files for Controller).
Instantiate the Controller
class (Classes
→ Instantiate Controller).
Drag a text view out to the application’s main window, and set its Autosizing attributes so that it will resize along with the window that contains it.
Control-drag a connection from the Controller instance object to the
text view, as shown in Figure 16-3, and connect it
to the textView
outlet.
Add a Show Info menu item to the MainMenu’s Window menu, as shown in Figure 16-4. Give it a key equivalent of
-I. This will let the user of our application get the info panel either by using the menu item or by just using the Command-key equivalent.
From the Cocoa-Menus palette:
Drag an Item to the menu, and place it as shown in Figure 16-4.
Use the Info panel to change its name and assign a Command-key equivalent.
Connect the Show Info menu item to the Controller’s
showInfoPanel:
action method by Control-dragging a
connection from the menu item to the Controller
object, as shown in Figure 16-5.
Save (
-S) and close (
Now that we’ve created the controller object for the application, the application’s main window with a text view, and the menu item for the user to request the info panel, we need to create a new nib file to contain the user interface for the info panel.
Create a new nib file in Interface Builder (File → New). Interface builder will show a dialog box similar to that in Figure 16-6. Select Empty and click New.
Save the file as InfoPanel.nib in your ~/LearningCocoa/Simple Inspector /English.lproj folder, as shown in Figure 16-7. You will be prompted to add the nib file to the Simple Inspector target; click the Add button to do so.
Now, we need to designate our Controller
class as
the object that will be the File’s Owner for this
nib file at runtime. To let Interface Builder know that the
Controller
class exists, drag the
Controller.h file from Project Builder onto the
InfoPanel.nib window.
Select the File’s Owner proxy object in the
InfoPanel.nib window, and, using the File’s Owner
Info inspector as shown in Figure 16-8, set the
File’s Owner class to Controller
.
This tells Interface Builder that the class responsible for loading
this nib file will be of type Controller. By setting this class as
the File’s Owner, we can designate that various
interface components in this nib file should be connected to the
outlets of the Controller
class.
Drag out a panel from the Windows palette. Name the panel Info by using the NSPanel Info inspector, as shown in Figure 16-9. Also, make sure that the Hide on deactivate and Utility window (Panel only) options are checked. These options will make our panel look like an Info panel and ensure that it disappears when our application is not active.
Drag two text fields onto the panel; name them
“Length of Text View:” and
“Number”. Control-drag a connection
from the File’s Owner proxy object (remember, this
is a stand-in for our Controller class) to the Number field, and set
the textLengthField
outlet, as shown in Figure 16-10.
Control-drag a connection from the File’s Owner
object to the Panel object in the InfoPanel.nib window, as shown in
Figure 16-11, and connect it to the
infoPanel
outlet.
Save the nib file (
Now that we have our two nib files designed, it’s time to implement the code that ties all the pieces together.
Edit the Controller.m file as follows:
#import "Controller.h" @implementation Controller - (IBAction)showInfoPanel:(id)sender { if (!infoPanelController) { // a [NSBundle loadNibNamed:@"InfoPanel" owner:self]; // b infoPanelController = [[NSWindowController alloc] // c initWithWindow:infoPanel]; } [textLengthField setIntValue:[[textView textStorage] length]]; // d [infoPanelController showWindow:self]; // e } @end
The code we added performs the following tasks:
Checks to see if we have a reference to a window controller for the Info panel. If we don’t, it means that we need to load the nib file that contains the panel. On the other hand, if we have a reference, then we don’t need to load it.
Loads the InfoPanel nib file. Notice that we don’t
use the .nib extension here. When the nib is
loaded, the connections assigned to the File’s Owner
will be made to the Controller
object.
Creates a new window controller, assigns it to the
infoPanelController
variable, and initializes it
to use the panel loaded from the nib.
Sets the textLengthField
to the length of the
textStorage
object.
Shows the Info panel.
Build and run (
-R) the application. Enter some text into the text view, then show the Info panel (Window → Show Info or
-I). You should see something like Figure 16-12.
Obviously, we could add all sorts of information to our inspector window, such as the number of words in the file, number of paragraphs, etc. We’ll leave these tasks as exercises at the end of the chapter.
Notice that we have a problem with our application. When you show the Info panel, you see the number of characters in the text view, but when you change the text in the text view, the info panel becomes out of date. The information only updates when you close the Info panel and select Show Info again. We see this behavior because we are only setting the information in the panel when the user tells the application to show the panel. We probably want this information to be updated dynamically as the text view’s contents change.
To get this functionality, we’ll use a notification that the text-view object will post to the notification center whenever its contents change. For more information about notifications, see Chapter 8.
Add the following code to the Controller.m file:
#import "Controller.h" @implementation Controller - (void)awakeFromNib // a { NSNotificationCenter * center = [NSNotificationCenter defaultCenter]; [center addObserver:self selector:@selector(textDidChange:) name:NSTextDidChangeNotification object:textView]; } - (void)textDidChange:(NSNotification *)notification // b { [textLengthField setIntValue:[[textView textStorage] length]]; } - (IBAction)showInfoPanel:(id)sender { if (!infoPanelController) { [NSBundle loadNibNamed:@"InfoPanel" owner:self]; infoPanelController = [[NSWindowController alloc] initWithWindow:infoPanel]; } [textLengthField setIntValue:[[textView textStorage] length]]; [infoPanelController showWindow:self]; } @end
The code we added performs the following tasks:
Adds an awakeFromNib
method that will add the
Controller instance as an observer to the default notification center
interested in NSTextDidChangeNotification
events
on the textView
object.
Implements the callback method that the notification center will call
whenever text changes in the text view. We simply update the
textLengthField
in our inspector panel.
Build and run (
-R) the application. Show the Info panel (Window → Show Info or
-I), then type text into the text view. The info panel will now keep up with the correct number of characters in the text view as you type.
Add a field to the Simple Inspector application that will display the number of words in a document.
Add a field to the Simple Inspector application that will display the number of paragraphs in a document.
Add an Info window to Dot View (see Chapter 8) that will let the user know the current diameter of the dot.