Most applications need to store and retrieve preferences that allow for user customization of an application’s behavior and keep track of configuration settings. Mac OS X provides, as part of its Core Foundation framework, a preferences system that provides a simple and standard way to maintain these preferences. Cocoa calls these preferences defaults.
If you go to the ~/Library/Preferences folder, you will see the user-preferences database used by the applications you run on your system. In fact, many of the applications we have created in this book have written preferences to this database. Take a look, and you’ll see that you probably have Dot View.plist, Menu.plist, and RTF Edit.plist files. These contain preferences used by some of the various Cocoa base classes used in the sample applications we’ve built throughout the book.
In this chapter we’ll show you how to take advantage of the user-preferences system from your applications using Cocoa.
The preference system in Mac OS X allows you to store values associated with a key—the name of a property—that can later be used to look up the preference value when you need it. These key-value pairs are scoped using a combination of username and application ID. All of the preferences for a user are stored in his ~/Library /Preferences folder.
There are multiple domains—or different scopes of coverage—in which a preference can exist. When you request a preference, the following resources are searched—in order—until a match is found:
The list of arguments that were passed to an application. This lets an application start up with a preference setting to override all other values for that preference’s key.
The users preferences stored in the ~/Library/Preferences folder. This is where preferences that are unique to a user and that need to last between invocations of an application are kept.
A set of global preferences used across all applications that a user may use. For example, rulers in text views will obtain a user’s preferred unit of measurement. These preferences are stored in the ~/Library/Preferences/GlobalPreferences.plist file. Many of these preferences are set using the System Preferences application.
The set of preferences that your application registers as the defaults for your application. If a value for a preference is not found anywhere else, then this allows your application to provide a default value.
Preferences in the ~/Library/Preferences folder are stored in the same property-list file format used by the Info.plist file included with application bundles. Preference value objects can be of any of the following types:
NSString
for storing string values
NSNumber
for storing number values derived from
integers, floats, and other numeric types
NSBoolean
for storing YES or NO values
NSDate
for storing date information
NSData
for storing arbitrary data
NSArray
for storing arrays that consists of any of
the previously listed types
NSDictionary
for storing name/value dictionaries
that have values of any of the previously listed types
To show how to use the
NSUserDefaults
class, the mechanism by which Cocoa
provides access to Mac OS X’s preference system,
we’ll build a simple application to keep track of a
few of our favorite things.
Create a new Cocoa application project in Project Builder (File → New Project → Application → Cocoa Application) named “Favorites”, and save it in your ~/LearningCocoa folder.
Open the MainMenu.nib file in Interface Builder.
Lay out the user interface as shown in Figure 15-1.
Create a subclass of NSObject
in Interface
Builder. Click on the Classes tab of the MainMenu.nib window, find
NSObject
, Control-click it, and select Subclass
NSObject from the pop-up menu. Name the subclass
“Controller”.
Create the following outlets on the Controller class:
bookField
colorField
foodField
cityField
Create an action named textFieldChanged:
. This
action will tell the preferences database when an item has changed.
Generate the source-code files for the Controller class (Classes → Create Files for Controller).
Instantiate the Controller class (Classes → Instantiate Controller).
Connect the four text fields on the user interface to their respective outlets by control-dragging a connection from the Controller instance to each of the fields in turn.
Connect each of the four text fields to the
Controller’s textFieldChanged
action method by control-dragging a connection from the text field to
the Controller instance.
Save (File → Save, or
-S) the nib file, and return to Project Builder.
Edit the Controller.h file as shown, adding an
instance variable that will hold a reference to a
NSUserDefaults
object.
#import <Cocoa/Cocoa.h>
@interface Controller : NSObject
{
IBOutlet id bookField;
IBOutlet id cityField;
IBOutlet id colorField;
IBOutlet id foodField;
NSUserDefaults * prefs;
}
- (IBAction)textFieldChanged:(id)sender;
@end
Add an init
method to the
Controller.m file. This will set the
prefs
instance variable.
- (id)init { [super init]; NSMutableDictionary * defaultPrefs = [NSMutableDictionary dictionary]; // a [defaultPrefs setObject:@"Learning Cocoa" forKey:@"FavBook"]; // b [defaultPrefs setObject:@"San Francisco" forKey:@"FavCity"]; [defaultPrefs setObject:@"Red" forKey:@"FavColor"]; [defaultPrefs setObject:@"Mexican" forKey:@"FavFood"]; prefs = [[NSUserDefaults standardUserDefaults] retain]; // c [prefs registerDefaults:defaultPrefs]; // d return self; }
This code does the following things:
Creates a new mutable dictionary that will serve as the container for the default values the application will use
Sets four key/value pairs that correspond to the default values we want to store in the preferences system
Obtains a reference to the preferences system
Indicates to the prefs
object that we want to use
the defaultPrefs
dictionary as the set of default
preferences
Add a dealloc
method so that the class cleans up
after itself properly.
- (void)dealloc { [prefs release]; [super dealloc]; }
Add an awakeFromNib
method to populate the user
interface from any settings that are in the prefs
object.
- (void)awakeFromNib { [bookField setStringValue:[prefs stringForKey:@"FavBook"]]; [cityField setStringValue:[prefs stringForKey:@"FavCity"]]; [colorField setStringValue:[prefs stringForKey:@"FavColor"]]; [foodField setStringValue:[prefs stringForKey:@"FavFood"]]; }
Implement the textFieldChanged:
action method so
that the key values are saved as they change to the
prefs
object.
- (IBAction)textFieldChanged:(id)sender { if (sender == bookField) { [prefs setObject:[bookField stringValue] forKey:@"FavBook"]; } else if (sender == cityField) { [prefs setObject:[cityField stringValue] forKey:@"FavCity"]; } else if (sender == colorField) { [prefs setObject:[colorField stringValue] forKey:@"FavColor"]; } else if (sender == foodField ){ [prefs setObject:[foodField stringValue] forKey:@"FavFood"]; } }
Build and run (
-R) the application. You should see the interface launch as shown in Figure 15-2.
Change some of the values. Quit the application, then restart to see if the values were saved.
Find the Favorites.plist file in your ~/Library/Preferences folder. Double-click the file to launch the Property List Editor (/Applications/Utilities), which allows you to see the values that you changed in the file, as shown in Figure 15-3. To see the XML representation of the plist file, click the Dump button. You can also, if needed, edit the property list here, save the changes, and then launch the application we built to see the changes.
You can access your preferences from the Terminal by using the defaults command. For example, to see the defaults for the Favorites application we just built, type the following into a Terminal window:
defaults read Favorites
Something like the following should print:
{ FavBook = "Stranger in a Strange Land"; FavCity = "Dallas Texas"; FavColor = Black; FavFood = Thai; }
If you want to modify a preference from the command line, you can use the defaults command’s write option. For example, to change our favorite city to Sedona, Arizona, issue the following command (the quotes are needed to accommodate the comma in the value string we are using):
defaults write Favorites FavCity "Sedona, Arizona"
Now go back to Project Builder, and build and run (
As we said before, preferences are stored in files using an application’s ID. So far, our sample applications haven’t specified a particular application ID, so the system has used the name of the application as its ID.
However, when you browse through your ~/Library/Preferences folder, you’ll see that quite a few of the files have long names like com.apple.iPhoto.plist. When Apple created the iPhoto application, they assigned it an application ID of com.apple.iPhoto—Apple’s domain name followed by the application name—to reduce the possibility of somebody else’s application named iPhoto interfering with the preferences used by the system. Obviously, for an application like iPhoto, there probably won’t be a collision. But for applications with more common names—like the ones that we have been creating in this book—this level of namespace protection is valuable.
For example, all of the Mac OS X applications created by O’Reilly & Associates should have application IDs that start with com.oreilly. Following this logic, our Favorites application should have an application ID of com.oreilly.Favorites. To specify this application ID:
Open up the main target of the application, navigate the outline view to Info.plist Entries → Simple View → Basic information, and change the Identifier field, as shown in Figure 15-4.
Build and run (
-R) the application, change the values, and quit the application.
Using the Finder, you can see that there is now a com.oreilly.Favorites.plist file in your ~/Library/Preferences folder. If you wanted to read this file from the command line, you could use the following command:
defaults read com.oreilly.Favorites
Take a look at the contents of the Favorites preference list file using Property List Editor.
Add a reset button to the Favorites application that will reset the values to their original state.
Modify the Favorites application so that it reads its application defaults from a .plist file contained as a resource in its application bundle.
Modify the Currency Converter application from Chapter 5 to remember the exchange rate between invocations of the application.