oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Adding a Preferences Window to Your Application
Pages: 1, 2, 3, 4, 5, 6, 7

At this point awakeFromNib looks like the following:

- (void)awakeFromNib
  NSBundle *bundle;
  NSString *path;
  NSArray *identifiers;
  NSArray *checkBoxesArray;
  prefs = [[NSUserDefaults standardUserDefaults] retain];
  tableColumns = [[NSMutableDictionary alloc] init];

  recordsFile = [NSString stringWithString:@"~/Library/Preferences/AddressBookData.plist"];
  recordsFile = [[recordsFile stringByExpandingTildeInPath] retain];
  if ( [prefs arrayForKey:@"Addresses"] != nil )
    records = [[NSMutableArray alloc] initWithArray:[prefs arrayForKey:@"Addresses"]];
  else if ( [[NSFileManager defaultManager] fileExistsAtPath:recordsFile] == YES )
    records = [[NSMutableArray alloc] initWithContentsOfFile:recordsFile];
    records = [[NSMutableArray alloc] init];
  // Load the identifiers array from the bundle resources
  bundle = [NSBundle mainBundle];
  path = [bundle pathForResource:@"Identifiers" ofType:@"plist"];
  identifiers = [[NSArray alloc] initWithContentsOfFile:path];

  // create the check boxes dictionary to easily refer to controls in prefs window
  checkBoxesArray = [NSArray arrayWithObjects:firstNameCB, lastNameCB, emailCB, homePhoneCB, workPhoneCB, mobilePhoneCB, nil];
  checkBoxes = [NSDictionary dictionaryWithObjects:checkBoxesArray forKeys:identifiers];

  [self addInitialColumnsForIdentifiers:identifiers];
  if ( [prefs arrayForKey:@"User Columns"] != nil ) {
    userColumns = [[NSMutableArray alloc] initWithArray:[prefs arrayForKey:@"User Columns"]];
    [self initializeTableWithColumns:userColumns];
  } else {
    userColumns = [[NSMutableArray alloc] init];
    [firstNameCB setState:NSOnState];
    [lastNameCB setState:NSOnState];
    [emailCB setState:NSOnState];
    [homePhoneCB setState:NSOnState];

Finally, implement 'setColumn'

With the first two parts of our strategy in place, we can now easily add and remove columns in response to the user changing the preferences, and have these changes preserved in the user's preferences. All of this is done in the method setColumn. Since we didn't create new Controller files from Interface Builder, we have to add setColumn by hand. Let's look for a moment at this method to gain a clearer idea of how we're going to proceed with this implementation.

The method definition should be added to Controller.m:

- (IBAction)setColumn:(id)sender

Recall how we connected six different buttons to this single action message. The idea is that whenever the user switches one of those preference check boxes, setColumn will be invoked, and we'll be able to identify which check box was changed given the information provided in the sender argument, and thus determine which table column to add or remove. The distinguishing characteristic of the sender object that allows us to do this is the title of the button, which we've conveniently made to be the same as all of the column identifiers, which are also the same as the keys in the tableColumns dictionary! We can get the title string by sending a title message to the sender, and then we can store this string in a local variable, identifier:

NSString *identifier = [sender title];

Once we have the name of the column we're working with, we need to determine whether we should remove the indicated column or add it to the table view; this depends on the state of the button after it has been clicked. Check boxes have two possible states by default: NSOnState and NSOffState. We can obtain the state of the sender check box by sending it a state message, whose return value we can compare to the constants NSOnState and NSOffState to determine our course of action. After putting all of these pieces together, here is the implementation of setColumn:

- (IBAction)setColumn:(id)sender
  NSString *identifier = [sender title];
  NSTableColumn *column = [tableColumns objectForKey:identifier];

  if ( [sender state] == NSOnState ) {
    [tableView addTableColumn:column];
    [self saveTableColumnPrefs];
  } else if ( [sender state] == NSOffState ) {
    [tableView removeTableColumn:column];
    [self saveTableColumnPrefs];

So in the first line of this method we took the title of the sender button, and stored it in the NSString variable identifier. In the next line, we declare a temporary NSTableColumn variable, column, which we assign to the object returned from tableColumns that has the key-matching identifier.

Finally we check to see what the state of the check box is after the user has clicked it. If it is NSOnState, then we add the column to the tableView, and if it is NSOffState, we remove the column from the table view. The second if statement is actually a little redundant -- it could have just been an else statement because if it's not in NSOnState, then it must be NSOffState. However, I prefer the more explicit version -- just a personal preference.

Final thoughts

With that the basic framework of adding and removing columns both at start up based on saved preferences, and during runtime based on changing preferences is set. In the end, we have added five methods to our application, and we have extensively modified awakeFromNib. These five methods are:

  • addColumnForIdentifer:
  • addColumnsForIdentifiers:
  • initializeTableWithColumns:
  • setColumn
  • saveTableColumnPrefs

The project folder for the final application in this column can be downloaded here.

As a final bit of housekeeping in this column, you should go into the dealloc method and add any necessary releases to the instance objects we've created and used.

Learning CocoaLearning Cocoa
By Apple Computer, Inc.
Table of Contents
Sample Chapter
Full Description
Read Online -- Safari

This code will add and remove the columns, almost like we want it to. There are several things about this implementation that makes it incomplete in the sense that we're not going to get a good-looking table out of this. Primarily this is because the code does not take into account the widths of the columns If we fix this, then our setup could be quite good.

An obvious first enhancement to this code would be to store in user defaults more information than just a list of columns. We could instead store an array of dictionaries, where each dictionary has the potential to describe in detail how the user has each column configured.

For example, this dictionary could have a key named @"Identifier" that returns the identifier string of the column (which is the information contained in our original list). A second possible key that would greatly enhance the looks of our table is a key @"Width", which would allow us to store and restore the widths of the columns. The possibilities of further attributes to store go on. We could have a key-value pair to store how text should be aligned in the title or the column, or we could have another key-value pair that stores a Boolean, which might indicate whether a column should be editable. I leave the addition of these features to the reader. The source code for the next column will show my solution to this issue.

Speaking of the next column, we will be learning how to enhance our interface using that wonderful Mac OS X innovation -- sheets. See you next time!

Michael Beam is a software engineer in the energy industry specializing in seismic application development on Linux with C++ and Qt. He lives in Houston, Texas with his wife and son.

Read more Programming With Cocoa columns.

Return to the Mac DevCenter.