Chapter 9. Nib Files and NSWindowController

In RaiseMan, you are already using two nib files: MainMenu.nib and MyDocument.nib. MainMenu.nib is automatically loaded by NSApplication when the application first launches. MyDocument.nib is automatically loaded each time an instance of MyDocument is created. In this section, you will learn how to load nib files using NSWindowController.

Why would you want to load a nib file? Most commonly, your application will have several windows (such as a find panel and a preferences panel) that are used only occasionally. By putting off loading the nib until the window is needed, your application will launch faster. Furthermore, if the user never needs the window, your program will use less memory.

NSPanel

In this chapter, you will create a preferences panel. The panel will be an instance of NSPanel, which is a subclass of NSWindow. There really are not that many differences between a panel and a general window, but because a panel is meant to be auxiliary (as opposed to a document window), it acts a little differently.

  • A panel can become the key window, but without becoming the main window. For example, when the user brings up a print panel, he can type into it (it is key), but the document the user was looking at remains the main window (that is what will be printed). NSApplication has a mainWindow outlet and a keyWindow outlet. Both outlets point at the same window unless a panel is involved; panels do not typically become the main window.

  • If it has a close button, you can close a panel by pressing the Escape key.

  • Panels do not appear in the window list in the Window menu. After all, if the user is looking for a window, the user is probably looking for a document, not a panel.

All windows have a Boolean variable called hidesOnDeactivate. If it is set to YES, the window will hide itself when the application is not active. Most document windows have this variable set to NO; most auxilary panels have it set to YES. This mechanism reduces screen clutter. You can set hidesOnDeactivate using the window inspector in Interface Builder.

Adding a Panel to the Application

The preferences panel that you are going to add will not do anything except appear for now. In Chapter 10, however, you will learn about user defaults. In that chapter, you will make the preferences panel actually do something.

The preferences panel will be in its own nib file. You will create a subclass of NSWindowController called PreferenceController. An instance of PreferenceController will act as the controller for the preferences panel. When creating an auxiliary panel, it is important to remember that you may want to reuse it in the next application. Creating a class to act just as a controller and a nib that contains only the panel makes it easier to reuse the panel in another application. Hip programmers would say, “By making the application more modular, we can maximize reuse.” The modularity also makes it easier to divide tasks among several programmers. A manager can say, “Rex, you are in charge of the preferences panel. Only you may edit the nib file and the preference controller class.”

The objects on the preferences panel will be connected to the preference controller. In particular, the preference controller will be the target of a color well and the check box. The preferences panel will appear when the user clicks on the Preferences… menu item. When running, it will look like Figure 9.1.

Completed Application

Figure 9.1. Completed Application

Figure 9.2 presents a diagram of the objects that you will create and the nib files in which they will reside.

Object/Nib Diagram

Figure 9.2. Object/Nib Diagram

MainMenu.nib

Open your project and create a new Objective-C class named AppController. Edit AppController.h to look like this:

#import <Cocoa/Cocoa.h>
@class PreferenceController;

@interface AppController : NSObject {
    PreferenceController *preferenceController;
}
- (IBAction)showPreferencePanel:(id)sender;

@end

Notice the Objective-C syntax:

@class PreferenceController;

This tells the compiler that there is a class PreferenceController. You can then make the declaration

PreferenceController *preferenceController;

without importing the header file for PreferenceController. You could replace @class PreferenceController; with #import "PreferenceController.h". This statement would import the header, and the compiler would learn that PreferenceController was a class. Because the import command requires the compiler to parse more files, @class will often result in faster builds.

Note that you must always import the superclass's header file, because the compiler needs to know which instance variables are declared in the superclass. In this case, NSObject.h is imported by <Cocoa/Cocoa.h>.

Setting Up the Menu Item

Save AppController.h and drag it from Xcode into MainMenu.nib in Interface Builder (Figure 9.3).

Parse AppController.h

Figure 9.3. Parse AppController.h

You will see the AppController class in the nib's class browser (Figure 9.4).

Outlets and Actions of AppController

Figure 9.4. Outlets and Actions of AppController

Use the Classes menu to instantiate an instance of AppController (Figure 9.5).

Instantiate an Instance of AppController

Figure 9.5. Instantiate an Instance of AppController

Control-drag from the Preferences… menu item to the AppController. Make it the target and set the action to showPreferencePanel: (Figure 9.6).

Set the Target of the Menu Item

Figure 9.6. Set the Target of the Menu Item

Close the nib file.

AppController.m

Now you need to write the code for AppController. Make the contents of AppController.m look like this:

#import "AppController.h"
#import "PreferenceController.h"

@implementation AppController

- (IBAction)showPreferencePanel:(id)sender
{
    // Is preferenceController nil?
    if (!preferenceController) {
       preferenceController = [[PreferenceController alloc] init];
    }
    [preferenceController showWindow:self];
}
- (void)dealloc
{
    [preferenceController release];
    [super dealloc];
}
@end

Notice that this file creates the instance of PreferenceController only once. If the preferenceController variable is non-nil, it simply sends the message showWindow: to the existing instance.

Preferences.nib

In Xcode, choose New File… from the File menu, and create a new Objective-C NSWindowController subclass. Name it PreferenceController (Figure 9.7).

Create Files for PreferenceController

Figure 9.7. Create Files for PreferenceController

Edit PreferenceController.h to look like this:

#import <Cocoa/Cocoa.h>

@interface PreferenceController : NSWindowController {
    IBOutlet NSColorWell *colorWell;
    IBOutlet NSButton *checkbox;
}
- (IBAction)changeBackgroundColor:(id)sender;
- (IBAction)changeNewEmptyDoc:(id)sender;
@end

In Interface Builder, create a new nib file. Choose Empty as the starting point (Figure 9.8).

Create a New Nib File

Figure 9.8. Create a New Nib File

Save the new nib into the English.lproj directory in your project directory as Preferences.nib. When asked, insert the new nib into the project (Figure 9.9).

Add the Nib to the Project

Figure 9.9. Add the Nib to the Project

Drag the PreferenceController.h file into the empty nib file (Figure 9.10).

Parse PreferenceController.h

Figure 9.10. Parse PreferenceController.h

Bring up the info panel, select File's Owner, and set its class to PreferenceController (Figure 9.11).

Set File's Owner to an Instance of PreferenceController

Figure 9.11. Set File's Owner to an Instance of PreferenceController

File's Owner

When a nib file is loaded into an application that has been running for a while, the objects that already exist need to establish some connection to the objects read from the nib file. File's Owner provides this connection. File's Owner is a placeholder in a nib file for an object that will already exist when the nib file is loaded. An object loading a nib file will provide the owner object. The owner is put into the place that File's Owner represents. In your application, the owner will be the instance of PreferenceController that was created by the AppController.

The use of File's Owner is confusing to many people. You will not instantiate PreferenceController in the nib file. Instead, you have just informed the nib file that the owner (which will be provided when the nib file is loaded) is a PreferenceController.

Lay Out the User Interface

Create a new panel by dragging a panel from the palette window and dropping it anywhere on the screen (Figure 9.12).

Create an Instance of NSPanel

Figure 9.12. Create an Instance of NSPanel

Make the panel smaller and drop a color well and a check box on it. Label them as shown in Figure 9.13. (Check boxes have labels, but you will have to drag out a text field to label the color well.)

Completed Interface

Figure 9.13. Completed Interface

Set the target of the color well to be File's Owner (your PreferenceController) and set the action to be changeBackgroundColor: (Figure 9.14).

Set the Target of the Color Well

Figure 9.14. Set the Target of the Color Well

Also, make your PreferenceController be the target of the check box and set the action to be changeNewEmptyDoc:.

Set the colorWell outlet of File's Owner to the color well object. Set the checkbox outlet of File's Owner to the check box object. Set the window outlet of File's Owner to the panel (Figure 9.15).

Set the Window Outlet of the File's Owner

Figure 9.15. Set the Window Outlet of the File's Owner

Open the attributes inspector for the panel. Disable resizing.

Change the title on the window to Preferences. Save the nib file.

PreferenceController.m

In Xcode, edit the PreferenceController.m to look like this:

#import "PreferenceController.h"

@implementation PreferenceController

- (id)init
{
    self = [super initWithWindowNibName:@"Preferences"];
    return self;
}

- (void)windowDidLoad
{
    NSLog(@"Nib file is loaded");
}

- (IBAction)changeBackgroundColor:(id)sender
{
    NSLog(@"Color changed: %@", [sender color]);
}

- (IBAction)changeNewEmptyDoc:(id)sender
{
    NSLog(@"Checkbox changed %d", [sender state]);
}

@end

Note that you set the name of the nib file to be loaded in the init method. This nib file will be loaded automatically when it is needed. The instance of PreferenceController will be substituted for the File's Owner in the nib file.

After the nib file is loaded, the PreferenceController will be sent windowDidLoad. It offers an opportunity (similar to awakeFromNib or windowControllerDidLoadNib:) for the controller object to initialize the user interface objects that have been read from the nib file.

When sent showWindow: for the first time, the NSWindowController automatically loads the nib file and moves the window on screen and to the front. The nib file is loaded only once. When the user closes the preferences panel, it is moved off screen but not deallocated. The next time the user asks for the preferences panel, it is simply moved on screen.

The changeBackgroundColor: and checkboxChanged: methods are pretty boring right now—they simply print out a message. In Chapter 10, you will change them to update the user's defaults database.

Build and run the application. The new panel should appear, and altering the check box or color well should result in a message in the console (Figure 9.16).

Completed Application

Figure 9.16. Completed Application

The first time a user encounters a color well, it may seem confusing. If you click on the edge of the color well, the edge becomes highlighted, the color panel appears, and the well is in “active” mode.

For the More Curious: NSBundle

A bundle is a directory of resources that may be used by an application. Resources include things like images, sounds, compiled code, and nib files. The class NSBundle is a very elegant way of dealing with bundles.

Your application is a bundle. In Finder, an application looks to the user like any other file, but it is really a directory filled with nib files, compiled code, and other resources. We call this directory the main bundle of the application.

Some resources in a bundle can be localized. For example, you could have two different versions of foo.nib, one for English speakers and one for French speakers. The bundle would have two subdirectories: English.lproj and French.lproj. You would put an appropriate version of foo.nib in each. When your application asks the bundle to load foo.nib, if the user has set the preferred language to French, the bundle will automatically load the French version of foo.nib. We will cover localization in Chapter 13.

To get the main bundle of an application, use the following code:

NSBundle *myBundle = [NSBundle mainBundle];

This is the most commonly used bundle. If you need to access resources in another directory, however, you could ask for the bundle at a certain path:

NSBundle *goodBundle;
goodBundle = [NSBundle bundleWithPath:@"~/.myApp/Good.bundle"];

Once you have an NSBundle object, you can ask it for its resources:

// Extension is optional
NSString *path = [goodBundle pathForImageResource:@"Mom"];
NSImage *momPhoto = [[NSImage alloc] initWithContentsOfFile:path];

A bundle may have a library of code. By asking for a class from the bundle, the bundle will link in the library and search for a class by that name:

Class newClass = [goodBundle classNamed:@"Rover"];
id newInstance = [[newClass alloc] init];

If you do not know the name of any classes in the bundle, you can simply ask for the principal class:

Class aClass = [goodBundle principalClass];
id anInstance = [[aClass alloc] init];

As you see, NSBundle is handy in many ways. In this section, the NSBundle was actually responsible (behind the scenes) for loading the nib file. If you wanted to load a nib file without an NSWindowController, you could do it like this:

BOOL successful = [NSBundle loadNibNamed:@"FindPanel"
                                   owner:someObject];

Note that you would supply the object that will act as the File's Owner.

Challenge 1

Delete your color well and check box. Drop an NSTabView on the preferences panel. Put the color well under one tab, and put the check box under the other. Be sure to reconnect them.

Challenge 2

Create another nib file for a custom “About” panel. Use NSBundle to load it explicitly. To bring the window forward (and on screen if necessary), send it the message makeKeyAndOrderFront:.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset