userDefaults: automatically register defaults from Settings.bundle

If you manage you app settings via a Setting.bundle for Settings.app, you may have experienced some strange behaviour: Even if default values for all keys are defined in your bundle, these settings aren’t automatically honoured by iDevices. If you try to load one of these settings by

id someObject = [[NSUserDefaults standardUserDefaults] objectForKey:@"myKey"];

someObject will be nil, the value stored as default in Settings.bundle is ignored.

That’s kind of normal as the method mentioned above reads from userDefaults and not from Settings.bundle – and default values from Settings.bundle aren’t automatically copied to userDefaults: In fact, some settings (booleans, for example) are at least written to userDefaults the first time Settings.app is launched after installing the app. But other settings (such as NSNumbers) aren’t even written out then – they’re only copied to userDefaults after changing them to values different from the default values in Settings.bundle. To make a long story short: Defaults in Settings.bundle give defaults values for Settings.app. But they are of no help help when reading values from userDefaults in your own app.

But wouldn’t it be useful if default settings from Settings.bundle would automatically be written to userDefaults if the user hasn’t specified any setting yet, such as to be able to count on the defaults you specify in your Settings.bundle?

I searched for a solution and stumbled upon a thread on StackOverflow. The code suggested there is a nice starting point but it counts on ALL values being well written into userDefaults if ONE value is readable. That’s not quite reliable because, as I mentioned, e.g. NSNumbers are not written unless they are different from the defaults.

So I adapted the snippet from StackOverflow to check every single value mentioned in Settings.bundle: If it’s already readable from userDefaults, nothing rests to be done. If it’s not yet readable, the value from Settings.bundle is written to userDefaults.

As a result, defaults from Settings.bundle can finally be taken for granted: When reading the value for a certain key there will always be an return value – either the user’s setting or, ifthere is none yet, the default you specify in Settings.bundle.

- (void)registerDefaultsFromSettingsBundle
{
NSLog(@"Registering default values from Settings.bundle");
NSUserDefaults * defs = [NSUserDefaults standardUserDefaults];
[defs synchronize];

NSString *settingsBundle = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"bundle"];

if(!settingsBundle)
{
   NSLog(@"Could not find Settings.bundle");
   return;
}

NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:@"Root.plist"]];
NSArray *preferences = [settings objectForKey:@"PreferenceSpecifiers"];
NSMutableDictionary *defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];

for (NSDictionary *prefSpecification in preferences)
{
   NSString *key = [prefSpecification objectForKey:@"Key"];
   if (key)
   {
      // check if value readable in userDefaults
      id currentObject = [defs objectForKey:key];
      if (currentObject == nil)
      {
         // not readable: set value from Settings.bundle
         id objectToSet = [prefSpecification objectForKey:@"DefaultValue"];
         [defaultsToRegister setObject:objectToSet forKey:key];
         NSLog(@"Setting object %@ for key %@", objectToSet, key);
         }
      else
         {
         // already readable: don't touch
         NSLog(@"Key %@ is readable (value: %@), nothing written to defaults.", key, currentObject);
         }
      }
   }

[defs registerDefaults:defaultsToRegister];
[defaultsToRegister release];
[defs synchronize];
}

Tell me about your mileage!

This entry was posted in iOS, Octets, Programming. Bookmark the permalink.

11 Responses to userDefaults: automatically register defaults from Settings.bundle

  1. Emmly says:

    Real brain power on display. Tahkns for that answer!

  2. Abel de Beer says:

    Thanks for this stuff!!! :)

  3. Zoe says:

    Hi

    I’m using your code exactly ( literally copy & paste ) and I have weird things happening. The bool values are being accepted, but the multi-value ones – aren’t.

    Any ideas?

    I can send you a copy of the plist if that’s any help.

    Thankx

    • vieuxrenard says:

      sure, just send it to me … (ulf a ijure dot org). Maybe I’ll see the issue right away. However in these cases it’s always a good idea as well to step through the code in Xcode’s debugger to see why it doesn’t work as exspected. It does for me, even with multivalues.

      Cheers, Ulf

  4. sesarto says:

    Bravo!!!

  5. Maarten Moors says:

    Ty was very usefull

  6. Manuel says:

    It works perfect, nice job!!!.

  7. Derek says:

    Hi, good work. You can actually shorten it though. You don’t need to check whether a preference is already in the user defaults because the values you register are stored in the registration domain and returned only if the preference has not been set/changed. In other words, they are the defaults and any changes made in preferences will override then when you request a value. This is not obvious from the Apple doco, but this sentence from Accessing Preference Values, Registering Your App’s Default Preferences tells it all with regard to registerDefaults – “… This method places your custom default values in the NSRegistrationDomain domain, which causes them to be returned when a preference is not explicitly set.”

  8. Russell says:

    Thank you so much for that code. I am displaying settings in the app and in the Settings.app and had blank values if a user never went into the Settings.app. Adding your method to my code instantly solved the issue. Great work!