I'm adding a Settings.bundle file to my iOS application. It's very minimal with one or two settings. The plist file in the Settings.bundle specifies the defaults, to the Settings application. I've read that to make my iOS application aware of these defaults, I have to also code them into my application. This seems like repeating myself and leaving an opening for defaults to easily get out of sync as I modify the program.
I know I can register the defaults using the contents of plist file, like so:
[[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Defaults" ofType:@"plist"]]];
That also seems like repeating myself, though. Would it be possible to use the plist file from the Settings.bundle as the source of these defaults, so that I only specify defaults in 1 location?
I trued adjusting that load to look something like this:
[[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Settings.bundle/Root" ofType:@"plist"]]];
That did not work, though. Does anybody know if this is possible, and how to load that plist file?
Wanted to add this as a comment to Andy's answer, but Stack Overflow didn't let me (too long for a comment).
If you're using ObjC here's your version:
-(void)registerDefaults
{
NSString* pathString = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Settings.bundle"];
NSString* rootPath = [pathString stringByAppendingPathComponent:@"Root.plist"];
NSDictionary* settingsDict = [NSDictionary dictionaryWithContentsOfFile:rootPath];
NSArray* prefSpecifiers = settingsDict[@"PreferenceSpecifiers"] ;
NSMutableDictionary* defaults = [NSMutableDictionary dictionary];
for (NSDictionary* item in prefSpecifiers) {
NSString* theKey = item[@"Key"];
NSObject* defaultValue = item[@"DefaultValue"];
if (defaultValue && theKey) {
defaults[theKey] = defaultValue;
}
}
if (defaults.count > 0) {
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
}
}
Yes, it is possible. Here's how you would do it in Swift:
func loadDefaults() {
let pathStr = NSBundle.mainBundle().bundlePath
let settingsBundlePath = pathStr.stringByAppendingPathComponent("Settings.bundle")
let finalPath = settingsBundlePath.stringByAppendingPathComponent("Root.plist")
let settingsDict = NSDictionary(contentsOfFile: finalPath)
let prefSpecifierArray = settingsDict?.objectForKey("PreferenceSpecifiers") as NSArray
var defaults:[NSObject:AnyObject] = [:]
for prefItem in prefSpecifierArray {
if let key = prefItem.objectForKey("Key") as String! {
let defaultValue:AnyObject? = prefItem.objectForKey("DefaultValue")
defaults[key] = defaultValue
}
}
_userDefaults.registerDefaults(defaults)
}
That will load the DefaultValue from each PreferenceSpecifier with a Key. For example:
<dict>
<key>Type</key>
<string>PSMultiValueSpecifier</string>
<key>Title</key>
<string>Frequency</string>
<key>Key</key>
<string>frequency</string>
<key>DefaultValue</key>
<integer>15</integer>
<key>Titles</key>
<array>
<string>5 minutes</string>
<string>15 minutes</string>
<string>30 minutes</string>
<string>60 minutes</string>
</array>
<key>Values</key>
<array>
<integer>5</integer>
<integer>15</integer>
<integer>30</integer>
<integer>60</integer>
</array>
</dict>
The loadDefaults
function will load the default value of 15 into the user default for "frequency".
I recommend you call the loadDefaults
function your app delegate, from the didFinishLaunchingWithOptions
handler, like this:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
loadDefaults()
return true
}
Then later in your app, say in a UITableViewController
, you can load the user default value like this:
let kFrequency = "frequency"
frequency = _userDefaults.integerForKey(kFrequency)
That is not necessary, you can simply call the data in the plist by calling the key that it was assigned to in the plist, for example,
_buttonSelection = [[NSUserDefaults standardUserDefaults] stringForKey:@"buttonAction01"]; //_buttonSelection is a NSString so that can be anything
Based on Andy's answer above, adapted for Swift 4, and avoiding the use of force-unwrap/force-cast:
private func loadDefaults() {
let userDefaults = UserDefaults.standard
let pathStr = Bundle.main.bundlePath
let settingsBundlePath = (pathStr as NSString).appendingPathComponent("Settings.bundle")
let finalPath = (settingsBundlePath as NSString).appendingPathComponent("Root.plist")
let settingsDict = NSDictionary(contentsOfFile: finalPath)
guard let prefSpecifierArray = settingsDict?.object(forKey: "PreferenceSpecifiers") as? [[String: Any]] else {
return
}
var defaults = [String: Any]()
for prefItem in prefSpecifierArray {
guard let key = prefItem["Key"] as? String else {
continue
}
defaults[key] = prefItem["DefaultValue"]
}
userDefaults.register(defaults: defaults)
}