oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Adding a New Style Preferences Window to Your App, Part 1

by Martin Redington

A preferences window is a staple feature of most Mac OS X applications.

For applications with a small number of preference settings, the required controls can be grouped within a single view, but for more complex applications, the Apple human interface guidelines recommend "using a toolbar within the preferences window in which each item in the toolbar changes the content of the main window," in the same way as the Finder, Safari, Mail, iChat, and many other Apple applications. Figure 1 below shows the Safari preferences window, with the Advanced preference pane selected.

In this pair of articles, I will show you how to add a new style preferences window to your application, that behaves in all respects exactly like the Apple preferences windows. In this part, I will cover the scaffolding necessary to support a new style preferences window. In part two, I will show you how to create the actual preference panes.

Using SS_PrefsController

Laziness is one of the three great virtues of a computer programmer, so I was very happy to discover that Matt Gemmell has already written the SS_PrefsController package, which does most of what we need.

To implement a preferences window using SS_PrefsController, you define each preference pane its own separate bundle, which contains a nib file with a custom NSView for the preference pane, and a class (which we will refer to as the PreferencePaneController) that implements the SS_PreferencePaneProtocol. The protocol's methods allow the controller to access necessary information about pane, such as the name and icon to display in the toolbar, and the tooltip to use.


Figure 1. The new style preferences window from Safari 2.0.2

The preferences window, and the set of preference panes, are managed by an instance of the SS_PrefsController class. When initializing this object, we tell it where to look for the preference pane bundles, and what suffix it should expect them to have. As if by magic, our modern style preferences window, complete with toolbar, icons, and automatic window resizing will appear.

However, the SS_PrefsController approach leaves a little to be desired.

Firstly, as implemented, every PreferencePaneController needs to implement the SS_PreferencePaneProtocol. This results in a lot of duplication, which could be avoided if the implementations of the protocol's methods could be inherited from a superclass. Furthermore, rather than hardcoding the underlying preference pane variables, we should be able to read them in as parameters from a configuration file of some kind.

Secondly, as written, SS_PrefsController does not highlight the icon for the selected pane correctly. This highlighting provides useful feedback to the user about which preferences pane is currently selected.

I will discuss how to remedy both of these issues below.

A New Application

Not every application needs a preferences window, but every preferences window needs an application.

In Xcode, create a new Cocoa application, called "NewPreferencesExample." To provide an entry point for the application, we add a new Objective-C class, NPEController, to the Classes group in the "Groups and Folders" pane in XCode.

NPEController has two preferences, preferenceOne (a Boolean on/off preference) and preferenceTwo (an integer preference, with three possible values--one, two, or three), as well as a handle to the NSUserDefaults object, and accessors and mutators (getters and setters) for each preference. Note that we don't have any class variables corresponding to preferenceOne and preferenceTwo; instead, we have a handle to an NSUserDefaults object.

The source for NPEController.h is as follows:

#import <Cocoa/Cocoa.h>

@interface NPEController : NSObject {
    NSUserDefaults *_defaults;

- (BOOL) preferenceOne;
- (void) setPreferenceOne:(BOOL) preferenceOne;

- (int) preferenceTwo;
- (void) setPreferenceTwo:(int) preferenceTwo;


The implementation, NPEController.m, is a little more complicated. In the awakeFromNib method, we obtain the user defaults, and register default values for each of our preferences. For neatness' sake, we also add a dealloc method, which releases the NSUserDefaults object after saving it to disk with the synchronize message. We also add applicationWillTerminate, an NSApplication delegate method, so that our controller is released (and hence dealloc is called, saving our preferences, just before we quit).

The accessor and mutator methods read from and write to the NSUserDefaults object directly, with setPreferencesTwo using an assertion to verify that the supplied value is legal.

#import "NPEController.h"

#define PREF_ONE_KEY @"preferenceOne"
#define PREF_TWO_KEY @"preferenceTwo"

@implementation NPEController

- (void) awakeFromNib{
    // set our default prefs.
    _defaults = [[NSUserDefaults standardUserDefaults] retain];
    NSDictionary *defaultPreferences = 
        [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], PREF_ONE_KEY,
                                                   [NSNumber numberWithInt:0], PREF_TWO_KEY, nil];
    [_defaults registerDefaults:defaultPreferences];

- (void) dealloc{
    [_defaults synchronize];
    [_defaults release];
    [super dealloc];

- (void) applicationWillTerminate:(NSNotification *)notification{
    [self release]; // to make sure we do get released.

- (BOOL) preferenceOne{ return [_defaults boolForKey:PREF_ONE_KEY]; }

- (void) setPreferenceOne:(BOOL) preferenceOne{
    [_defaults setBool:preferenceOne forKey:PREF_ONE_KEY];

- (int) preferenceTwo{ return [[_defaults objectForKey:PREF_TWO_KEY] intValue]; }

- (void) setPreferenceTwo:(int) preferenceTwo{
    NSAssert(preferenceTwo >= 0 && preferenceTwo <= 3, 
               @"preferenceTwo was less than 0 or greater than 3");

    [_defaults setInteger:preferenceTwo forKey:PREF_TWO_KEY];


That is all the code required for the application. Of course, a real-world application would do more than this, and would probably isolate the preferences off in a separate class from the main application controller, but for our purposes, this is sufficient.

The final step that we need to take is to open MainMenu.nib (which is in the Resources group of our Xcode project). Switching to the Classes tab of MainMenu.nib in Interface Builder, we read in NPEController.h, and instantiate an NPEController object.

In the Instances tab of Interface Builder, we make a connection from File's Owner to our NPEController object, setting it to be the delegate for the File's Owner (which is the NSApplication).

Running Mac OS X Tiger

Related Reading

Running Mac OS X Tiger
A No-Compromise Power User's Guide to the Mac
By Jason Deraleau, James Duncan Davidson

Pages: 1, 2, 3, 4

Next Pagearrow