|
|
|||||||||
![]() |
|||||||||
|
![]() |
||||||||
|
Cocoa Preferences Window Tutorial Wednesday, March 14, 2007
Another option is to use the DBPrefsWindowController class I posted a few days ago. It was designed to be easier to work with than these other options. Internally it works just like John Devor’s example code, but all of the details are handled by the class. You just need to subclass it and override one method with a few lines of code. It also provides a shiny cross-fade when switching between views in the preferences window.
If you’re comfortable with subclassing, creating nib files and custom views, and reverse engineering example projects, then just download the DBPrefsWindowController project and dig in. However, if you’d prefer step-by-step instructions for adding a preferences window to your project, this tutorial is for you.
In this tutorial, I’ll walk you through the process of adding the DBPrefsWindowController class to your project, subclassing it, creating a nib file and custom views, using Cocoa Bindings in Interface Builder, adding the toolbar icons to your project, and then finally working with NSUserDefaults to make decisions in your application. When we’re finished, you’ll have a preferences window in your application like the one shown at the top of this blog entry.
Step One
We’ll need a few files from the DBPrefsWindowController project, so download it now:
Launch it, play with it, get that out of your system, then come back here and do the tutorial.
Add the Classes to Your Project
Create a new Cocoa application in Xcode, or open an existing project to which you want to add a preferences window. For this tutorial, we’ve named the application “MyCoolProgram.”
Control-click on the Classes folder in the Groups & Files area of Xcode and select “Add->Existing Files...” from the contextual menu.
In the select dialog that appears, navigate to the Source folder from the DBPrefsWindowController example database and select the DBPrefsWindowController.m and DBPrefsWindowController.h files. You can select both of them at the same time in this dialog. Click the Add button. Another dialog will appear. Click the “Copy items blah blah blah...” checkbox at the top of the dialog. Leave everything else as-is, then click the Add button.
Right-click on the Classes folder again, this time selecting “New File...” from the contextual menu. In the Assistant dialog that appears, select “Objective-C class” and click Next. Name the new file “MyPrefsWindowController.m” and click the Finish button.
Note: We could have selected “Objective-C NSWindowController subclass” as the new file type, since what we’re creating will be a subclass of an NSWindowController, but there’s really no benefit to selecting that option here. The only difference between the two file types is one word, and we’re going to modify that word anyway.
At this point your project should look something like this:
Now we want to change our MyPrefsWindowController class from a subclass of NSObject to a subclass of DBPrefsWindowController. In the MyPrefsWindowController.h file, add #import "DBPrefsWindowController.h" after the #import <Cocoa/Cocoa.h> line. Then on the line that starts with @interface and change NSObject to DBPrefsWindowController.
Now we need to add some instance variables to point to the custom views we’ll be creating in Interface Builder. So right under the line we just modified, we’ll add two IBOutlets to NSViews. Modify your MyPrefsWindowController.h file so it looks like this:
#import <Cocoa/Cocoa.h> #import "DBPrefsWindowController.h" @interface MyPrefsWindowController : DBPrefsWindowController { IBOutlet NSView *generalPrefsView; IBOutlet NSView *advancedPrefsView; } @end
After you’ve modified this file, save your changes by selecting Save from the File menu. If you continue to the next step without first saving the file, your nib file will think it’s still an NSObject subclass, and nothing will work properly.
Create a New Nib File
Next we’re going to create the preference panels using Interface Builder. Launch Interface Builder and select New from the File menu. In the dialog that appears, select Empty, then click the New button.
Position this new nib window and the Xcode window so you can see both at the same time. Then drag the DBPrefsWindowController.h file to the nib window in Interface Builder. The window view will change from the Instances view to the Classes view—don’t worry about that. Next, drag the MyPrefsWindowController.h file to your window in Interface Builder.
Note: You must drag the files in this order. If you try to drag MyPrefsWindowController.h first, Interface Builder will complain because it doesn’t yet know about the DBPrefsWindowController class.
The classes should look like this in the Interface Builder nib file window:
Now save your new nib file by selecting Save from the File menu. Name the file “Preferences” and save it in your project folder. It’s important that the file name be “Preferences,” since DBPrefsWindowController will look for a nib file with this name. Leave the “10.2 and later format” checkbox selected and click the Save button. Another dialog should appear asking if you want to add the nib file to your project. Make sure the checkbox next to your application name is selected and click the Add button.
Click the Instances tab button to switch back to the icon view of your nib. Highlight the File’s Owner icon and press Command-5. This will open up the Inspector window and switch it to the Custom Class area. Change the file’s owner from NSObject to MyPrefsWindowController.
Create the Custom Views
Now we’re going to create one custom view for each button in the preferences window toolbar. Switch to the Container Views page in the palette and drag the first Custom View to the nib window.
Name the new view “General” by double-clicking its name under the icon.
Drag a checkbox (it’s labeled Switch) from the palette to your new custom view and change the label to “Show Message at Startup.”
Make the window only as tall as it needs to be for the single button. Then type Command-3 to display the Size view in the Inspector window and set the view’s width to 300. The DBPrefsWindowController class will change the window size to match this view size. A well designed preferences window will change only its height, not its width, so we’ll set all of our views to the same width.
Select the Show Message at Startup checkbox and type Command-4 to display the Bindings information in the Inspector window. Click on “value” and then click on the Bind checkbox. Make sure Bind To is set to Shared User Defaults, leave Controller Key set to “values,” and then type “MsgAtStartup” into the Model Key Path field. Thanks to Cocoa Bindings, this is all the wiring you need to do to save this preference in the user’s preferences file.
Next, drag another custom view to the nib window. Name this one “Advanced” and add a few checkboxes or other controls to it. If you want, you can set some bindings for these objects too, or just save that for later. Set the view to a different height than the General view (just so you can watch the window resize later), but give it a width of 300.
You’ll notice that there’s now a Shared Defaults icon in your nib. Interface Builder added this for you when you bound the checkbox to Shared User Defaults. Your nib should now look something like this:
Bring the nib window to the front and, while holding down the Control key, drag from the File’s Owner icon to the General icon. Release the mouse and the Inspector window will display the Connections options. Double-click on “generalPrefsView” to connect your custom view to the outlet you added to the MyPrefsWindowController.h file. Now repeat this process to connect the Advanced view to the advancedPrefsView outlet.
Save your nib file again by selecting Save from the File menu.
Wire Up the Toolbar Buttons
Switch back to Xcode so we can edit the MyPrefsWindowController.m file. Add the following code between the @implementation and @end lines:
- (void)setupToolbar { [self addView:generalPrefsView label:@"General"]; [self addView:advancedPrefsView label:@"Advanced"]; }
This method will be called by DBPrefsWindowController just before it displays the toolbar.
Now we need to add some icons to our project to display in the preferences window toolbar. Switch to the Finder and open the DBPrefsWindowController folder and find the Resources folder. Inside that folder you’ll find a General and an Advanced image file. Drag these onto the Resources folder in your Xcode project window. A dialog will appear, in which you should click the “Copy items blah blah blah...” checkbox. Then click the OK button.
At this point you should be able to successfully build and run your project. Unless you’re human, in which case you made a typo or missed something and you’ve got errors to fix. So go ahead and build your project and fix any errors, I’ll wait...
Create an Application Delegate
Naturally you tried running your project too, and realized that you can’t yet display the preferences window. So let’s wire up the Preferences menu so it displays the preferences window.
We’re going to need an application delegate for this. If you started with an existing application and already have one, then you can kick-back and relax until the next section.
Control-click on the Classes folder and select New File from the contextual menu again. Create another Objective-C class and name it “AppController.m.”
Now drag the “AppController.h” file from Xcode’s Groups & Files list to the MainMenu window in Interface Builder. Interface Builder should switch to the Classes view to show you that the class has been added.
While AppController is still highlighted in Interface Builder, select Instantiate AppController from the Classes menu. Interface Builder will switch back to the Instances view, and you should see a new AppController icon there.
Hold down the Control key and drag from the File’s Owner icon to the AppController icon. Release the mouse, and the Inspector window will appear set to the Connections view. Make sure the Outlets tab is selected, then highlight “delegate” and click the Connect button. You’ve just set AppController as the application delegate.
Save your changes to the nib file and switch back to Xcode.
Connect the Preferences Menu
For those of you that already had an application delegate, welcome back. Now we’ll add a method to our application delegate to open the preferences window.
At the top of the application delegate’s “.m” file (AppController.m for those who just created the delegate), right after the #import "AppController.h" (or equivalent) line, add #import "MyPrefsWindowController.h". Then add the following method between the @implementation and @end lines:
- (IBAction)openPreferencesWindow:(id)sender {
[[MyPrefsWindowController sharedPrefsWindowController] showWindow:nil];
Now switch to the “.h” file and add this after closing curly brace and before the @end line:
- (IBAction)openPreferencesWindow:(id)sender;
Save the “.h” file (that’s an important step!) and drag its icon from the Groups & Files list to the MainMenu.nib window that’s open in Interface Builder. Switch to Interface Builder and double-click on the MainMenu icon to display the menu window. Click on the first menu title to expand it so you can see the Preferences item. Now hold down the Control key and drag from the Preferences menu item to the AppController icon in the nib window. When you release the mouse, the Inspector window will switch to the Target/Action tab of the Connections settings. Highlight the openPreferencesWindow: action and click the Connect button.
Save your changes and switch back to Xcode and try building and running your program. This time, selecting Preferences from the application menu should display the preferences window. If you select the Show Message at Startup checkbox, then quit and relaunch the application, the checkbox should still be selected. If something’s not working properly, check the Troubleshooting section below for hints.
Acting on Preferences
The last thing we want to do is modify the behavior of our program based on a preference setting.
Add this code to your application delegate (Application.m):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"MsgAtStartup"]) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Hello, world!"]; [alert runModal]; [alert release]; } }
Remember that “MsgAtStartup” is the value we typed into the Inspector window when we were setting up the bindings for the Show Message at Startup checkbox. That’s our connection between the checkbox in the preferences window and the action we’re taking here.
Run the project. If the message doesn’t appear at startup, open the preferences window and enable the checkbox. Then quit the application and try running it again. The message should appear this time.
Set the Default Preference
By default you’d probably want to display this message the first time the program is run. To do this, we need to specify the preference setting’s default value. The best place to do this is in the application delegate’s +initialize method. Add this code to your application delegate:
+ (void)initialize { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:@"YES" forKey:@"MsgAtStartup"]; [userDefaults registerDefaults:appDefaults]; }
Now if we delete the application’s preferences file, the next time we launch the application the message will display at startup, and the checkbox in the preferences window will be selected.
If you created a new application for this tutorial, and named it “MyCoolProgram” as suggested, then the application’s preferences file will be named com.yourcompany.MyCoolProgram.plist. You’ll find it in the ~/Library/Preferences folder.
If you’d prefer a different preferences file name, for example, if your company name isn’t “Your Company,” then you can change this in the application properties window.
Close the Info window and run the application again. This time, since it’s there’s no existing preferences file with this new name, the message window should display when the program is launched.
Ta-Da!
That’s it. We’re done.
If you’re having any problems, see the Troubleshooting section below.
You might also want to download the example project here to see if you can find out what you missed.
If that doesn’t help, post your questions in the Comments section of this blog entry.
To learn more about setting and using user defaults, read User Defaults Programming Topics for Cocoa.
To learn more about Cocoa Bindings, read Cocoa Bindings Programming Topics.
Troubleshooting
If you get a message in the run log that says “Attempted to add a nil view when calling -addView:label:image:,” it probably means your nib file isn’t getting properly loaded. Make sure it’s named “Preferences.” Also make sure the IBOutlets are properly connected in Interface Builder.
If that doesn’t fix the problem, try dragging the MyPreferences.h file into the nib window again to make sure it has the latest version. Then double-check the connections to the views again. | |||||||||