oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Adding a New Style Preferences Window to Your App, Part 1
Pages: 1, 2, 3, 4

The Preferences Controller

Our NPEPreferencePaneController class will not work with SS_PrefsController. To get it to work, we will subclass SS_PrefsController, and add the necessary code in our subclass.

We do need to make one tiny hack to the SS_PrefsController code directly, though. The init method for SS_PrefsController calls the designated initializer: - (id)initWithPanesSearchPath:(NSString*)path bundleExtension:(NSString *)ext. We want to avoid this, so we will comment out the init method in SS_PrefsController.

Our NPEPrefsController class header looks like this:

#import <Cocoa/Cocoa.h>
#import "SS_PrefsController.h"
#import "NPEPreferencePaneController.h"

@interface NPEPrefsController : SS_PrefsController {


- (id)initWithPanesSearchPath:(NSString*)path bundleExtension:(NSString *)ext controller:(id)controller;
- (void)activatePane:(NSString*)path controller:(id)controller;

- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar;

- (void)showPreferencesWindow;
- (void)showPreferencePane:(NSString *) paneName;


The underlying implementation looks like this:

#import "NPEPrefsController.h"

@implementation NPEPrefsController

// Designated initializer

- (id)initWithPanesSearchPath:(NSString*)path bundleExtension:(NSString *)ext controller:(id)controller;
    if (self = [super init]) {
        [self setDebug:NO];
        preferencePanes = [[NSMutableDictionary alloc] init];
        panesOrder = [[NSMutableArray alloc] init];
        [self setToolbarDisplayMode:NSToolbarDisplayModeIconAndLabel];
        [self setToolbarSizeMode:NSToolbarSizeModeDefault];
        [self setUsesTexturedWindow:NO];
        [self setAlwaysShowsToolbar:NO];
        [self setAlwaysOpensCentered:YES];
        if (!ext || [ext isEqualToString:@""]) {
            bundleExtension = [[NSString alloc] initWithString:@"preferencePane"];
        } else {
            bundleExtension = [ext retain];
        if (!path || [path isEqualToString:@""]) {
            searchPath = [[NSString alloc] initWithString:[[NSBundle mainBundle] resourcePath]];
        } else {
            searchPath = [path retain];
        // Read PreferencePanes - this is where we differ from SS_PrefsController

        if (searchPath) {
            NSEnumerator* enumerator = [[NSBundle pathsForResourcesOfType:bundleExtension inDirectory:searchPath] objectEnumerator];
            NSString* panePath;
            while ((panePath = [enumerator nextObject])) {
                [self activatePane:panePath controller:controller];
        return self;
    return nil;

- (void)activatePane:(NSString*)path controller:(id)controller{
    NSBundle* paneBundle = [NSBundle bundleWithPath:path];
    NSAssert1(paneBundle != nil, @"Could not initialize bundle: %@", paneBundle);
    NSDictionary* paneDict = [paneBundle infoDictionary];
    NSString* paneClassName = [paneDict objectForKey:@"NSPrincipalClass"];
    NSAssert1(paneClassName != nil, @"Could not obtain name of Principal Class for bundle: %@", paneBundle);    
    Class paneClass = NSClassFromString(paneClassName);
    NSAssert2(paneClass == nil, @"Did not load bundle: %@ because its Principal Class %@ was already used in another Preference pane.", paneBundle, paneClassName);
    paneClass = [paneBundle principalClass];
    NSAssert2([paneClass isSubclassOfClass:[NPEPreferencePaneController class]], 
              @"Could not load bundle %@ because it Principal Class %@ is not a subclass of NPEPreferencePaneController",
              paneBundle, paneClassName);
    NSString *nibName = [paneDict objectForKey:@"NSMainNibFile"];
    NSAssert1(nibName, @"Could not obtain name of nib for bundle: %@", paneBundle);
    NPEPreferencePaneController *aPane = [[paneClass alloc] initWithNib:nibName dictionary:paneDict controller:controller];    
    if(aPane != nil){
        [panesOrder addObject:[aPane paneName]];
        [preferencePanes setObject:aPane forKey:[aPane paneName]];
        [aPane release];

- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar{
    return panesOrder;

// make sure we get a highlighted icon first time around.

- (void)showPreferencesWindow{
    [super showPreferencesWindow];
    [prefsToolbar setSelectedItemIdentifier:[prefsWindow title]];    

// make sure we get a highlighted icon when activating a pane programmatically.

- (void)showPreferencePane:(NSString *) paneName{
    [self loadPreferencePaneNamed:paneName];        
    [prefsToolbar setSelectedItemIdentifier:paneName];    


The initWithPanesSearchPath and activatePane methods are lifted almost wholesale from SS_PrefsController. The only significant changes that we have made are to ensure that the preference pane controller classes are subclasses of our NPEPreferencePane controller class, instead of conforming to the SS_PreferencePaneProtocol, and to add an additional controller parameter to each method, so that we can pass a handle to the controller down to the preference pane controller implementations.

The remaining three functions are all related to icon highlighting: toolbarSelectableItemIdentifiers is an NSToolBar delegate method, that returns an array of pane name, which should be highlighted when they are selected (all of them, in our case), and we override the showPreferencesWindow method of SS_PrefsController to ensure that our initially selected pane's icon is highlighted. Finally, the showPreferencesPane method is unique to NPEPrefsController--it allows us to display a pane programmatically, and handles the correct icon highlighting. Note that highlighting only works in Mac OS X 10.3 or later.

Pages: 1, 2, 3, 4

Next Pagearrow