34

I have an AppDelegate class with +(void)initialize method that I use to register some defaults. Here's the code that I use:

+ (void)initialize {
  NSDictionary *defaults = [NSDictionary dictionaryWithObjectsAndKeys:@"NO", @"fooKey", @"YES", @"barKey", nil];

  [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
}

I also created Preferences.xib which holds couple of checkboxes (NSButton) that display status of preferences. They are bound to NSUserDefaultsController with same keys (fooKey and barKey in this case). Each time I launch an app and change the "defaults" they are restored on next app launch.

Is there a way to register "default defaults" without overwriting already existing values? Maybe each time I build and launch an app its preferences file is being recreated? Maybe I should unbind checkboxes from NSUserDefaultsController and maintain the values of keys myself with some custom code in preferences window controller?

I'd like to hear your implementation of choice for maintaining user defaults.

I'm using Mac OS X 10.6.2 and XCode 3.2.1

Eimantas
  • 48,927
  • 17
  • 132
  • 168
  • Make sure the checkboxes are bound to "Shared User Defaults Controller", controller key "values", model key paths "fooKey"/"barKey". Otherwise your code looks alright to me. – Costique Jan 16 '10 at 09:53
  • 5
    `@"NO"` is not a Boolean literal; it is a string literal for the word “NO”. You should use `[NSNumber numberWithBool:NO]`; otherwise, the values in the user defaults will not be Booleans. – Peter Hosey Jan 16 '10 at 17:37
  • No, they won't be, but if I read them as BOOLs - they will. Excerpt from Daniel H Steinberg's book "Cocoa Programming" (pragprog.com): Actually, you don’t need to be so explicit when you place the BOOL in the dictionary. You could pass in the value YES or NO as a string and still create a BOOL from this value when you read from the preferences. – Eimantas Jan 16 '10 at 17:54
  • 1
    Note that implementations of `+ (void)initialize` should ALWAYS start with `if (self != [YourClass class]) { return; }`, else it will be called multiple times if your subclasses don't implement their own `+ (void)initialize` methods. Another thing to keep in mind is to use `[YourClass class]` not `[self class]`. (One of the very places where the former is the correct way of calling a class's own method.) All this is because the runtime treats `+ (void)initialize` in a special way: there is no overloading. Every single class gets its implementation (or superclass's if none provided) called. – Regexident Jan 28 '13 at 02:43

1 Answers1

75

From the documentation for -registerDefaults: (emphasis added):

The contents of the registration domain are not written to disk; you need to call this method each time your application starts. You can place a plist file in the application's Resources directory and call registerDefaults: with the contents that you read in from that file.

So your code was on the right track. This is how you register default defaults.

I usually use this in -applicationDidFinishLaunching::

// Load default defaults
[[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Defaults" ofType:@"plist"]]];

Using a plist makes it easy to add and change defaults in your app, and it prevents you from making the mistake of using @"NO" as a value too.

Edit: Swift 3 variant:

 UserDefaults.standard.register(defaults: NSDictionary(contentsOf: Bundle.main.url(forResource: "Defaults", withExtension: "plist")!)! as! [String : Any])
Johan Kool
  • 15,637
  • 8
  • 64
  • 81
  • Johan - thanks for the eye-opener. I rechecked the call and it seems that it works without any if statements! – Eimantas Jan 17 '10 at 07:13
  • Can your "load default defaults" code above be used with the Settings bundle? For example, could I use the Root.plist file to initialize my defaults? – Ricky Feb 05 '10 at 16:23
  • Yes, though your Settings bundle may be loaded before you app was ever launched, so you still may want to set the initial defaults in the settings plist as well. The `-registerDefaults:` method will not override preferences set through a Settings bundle. – Johan Kool Jun 10 '10 at 23:16
  • 2
    For some reason, when I had this in -applicationWillFinishLaunching, it didn't work. Simply moving it to -applicationDidFinishLaunching did the trick. Weird. – geerlingguy Jan 26 '12 at 23:50
  • @JohanKool if registerDefaults does not write to disk? Why should I use this? I can keep the dictionary (got from Bundle plist) on my own – onmyway133 Nov 07 '13 at 10:03
  • It does not work for me :( I used @"Settings.bundle/Root" instead of @"Defaults". Is it correct? – Charles Jan 07 '14 at 12:21
  • As workaround I used the function registerDefaultsFromSettingsBundle defined here: http://ijure.org/wp/archives/179 – Charles Jan 07 '14 at 13:03