10

I created a settings bundle with about 8 toggle switches. What I am trying to do it get the default values from the settings bundle. Currently right now I have these two methods:

func registerSettingsBundle(){
        let appDefaults = [String:AnyObject]()
        UserDefaults.standard.register(defaults: appDefaults)
        UserDefaults.standard.synchronize()
    }

    func updateDisplayFromDefaults(){
        let defaults = UserDefaults.standard
        let update_lot = defaults.bool(forKey: "update_lot")
        print(update_lot)
    }

and I am calling these methods in my viewDidLoad

override func viewDidLoad() {
        super.viewDidLoad()
        registerSettingsBundle()
        updateDisplayFromDefaults()
    }

However this does not get me the default values (which are all true, but they all return false). This works and gives me the correct values if I close down the app, open settings, adjust the settings and re-open the app. Is there away of getting the default settings? I went the route of reading the plist, but if I change the settings in my settings bundle, it would not take effect.

Hamish
  • 78,605
  • 19
  • 187
  • 280
user979331
  • 11,039
  • 73
  • 223
  • 418
  • Hello, I think you forgot default.set for key "update_lot" sample.code : UserDefaults.standard.set(true, forKey: "update_lot") – Vivek Sep 29 '17 at 19:10
  • Can you put an example in an answer please? – user979331 Sep 29 '17 at 19:15
  • Please check this example `UserDefaults.standardUserDefaults().setBool(true, forKey: "update_lot")` – Vivek Oct 03 '17 at 13:18
  • There are multiple posts on SO entertaining your issue. Don't ask similar questions that are already answered. https://stackoverflow.com/questions/29163955/ios-8-2-settings-bundle-default-value – PGDev Oct 05 '17 at 07:20
  • Does this answer your question? [Settings bundle values returning nil](https://stackoverflow.com/questions/36253998/settings-bundle-values-returning-nil) – jrc Mar 01 '20 at 18:58

5 Answers5

22

For the sake of the demonstration let's assume that you have two switches in the settings bundle. One with the default value set to YES and one with the default value set to NO.

Settings.bundle content

If you want to be able to access default values defined in the Settings.bundle from the UserDefaults in your app you have to register them first. Unfortunately, iOS won't do it for you and you have to take care of it by yourself.

The following method scans the Root.plist associated with the Settings.bundle and registers the default values for the identifiers of your preferences.

func registerDefaultsFromSettingsBundle()
{
    let settingsUrl = Bundle.main.url(forResource: "Settings", withExtension: "bundle")!.appendingPathComponent("Root.plist")
    let settingsPlist = NSDictionary(contentsOf:settingsUrl)!
    let preferences = settingsPlist["PreferenceSpecifiers"] as! [NSDictionary]
    
    var defaultsToRegister = Dictionary<String, Any>()
    
    for preference in preferences {
        guard let key = preference["Key"] as? String else {
            NSLog("Key not found")
            continue
        }
        defaultsToRegister[key] = preference["DefaultValue"]
    }
    UserDefaults.standard.register(defaults: defaultsToRegister)
}

I recommend running it as early as possible. You will be sure that the defaults are there for all parts of your app.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
    registerDefaultsFromSettingsBundle()
    
    let one = UserDefaults.standard.bool(forKey: "switch_one")
    let two = UserDefaults.standard.bool(forKey: "switch_two")
    
    NSLog("One: \(one), Two: \(two)")
    
    return true
}
Duck
  • 34,902
  • 47
  • 248
  • 470
Kamil Szostakowski
  • 2,153
  • 13
  • 19
9

this does not get me the default values (which are all true, but they all return false)

Looks like you have a toggle switch which displays as ON in the setting bundle and when you read bundle you get all the false value.

If this is the case then you are missing something here.

In setting bundle(Root.plist) we have "Default Value" field which is nothing to do with the actual default value of toggle switch. This is just a visual indicator to switch. You may have "Default value" set as "YES" in plist but when you try to read the value you will end up getting false.

enter image description here

Here I have set Default Value for Reminder in Root.plist as YES and for Update NO So that when app launch it shows as above.

But when I tried to read these defaults - it gives both as false.

func getDefaults() {        
    let stanDefaults = UserDefaults.standard
    print("Default value of Update - \(stanDefaults.bool(forKey: "update_lot_pref"))")
    print("\nDefault value of Reminder - \(stanDefaults.bool(forKey: "reminder_pref"))")
}

Default value of Update - false Default value of Reminder - false

Now, if you want to sync these values - default value in Root.plist and value of default - then you have to set it programmatically.

func setApplicationDefault() {
    let stanDefaults = UserDefaults.standard
    let appDefaults = ["reminder_pref": true]
    stanDefaults.register(defaults: appDefaults)
    stanDefaults.synchronize()
}

Here in my Root.plist I have default value as YES and also when viewDidload I set this preference value as true. When I run it gives me

Default value of Reminder - true

And this is how my Root.plist looks.

enter image description here

Hope it helps.

manismku
  • 2,160
  • 14
  • 24
4

Here's an answer based on @Kamil (many thanks for that) that doesn't rely on NSDictionary and uses PropertyListSerialization.

Swift 5

func registerDefaultsFromSettingsBundle() {
    let settingsName                    = "Settings"
    let settingsExtension               = "bundle"
    let settingsRootPlist               = "Root.plist"
    let settingsPreferencesItems        = "PreferenceSpecifiers"
    let settingsPreferenceKey           = "Key"
    let settingsPreferenceDefaultValue  = "DefaultValue"

    guard let settingsBundleURL = Bundle.main.url(forResource: settingsName, withExtension: settingsExtension),
        let settingsData = try? Data(contentsOf: settingsBundleURL.appendingPathComponent(settingsRootPlist)),
        let settingsPlist = try? PropertyListSerialization.propertyList(
            from: settingsData,
            options: [],
            format: nil) as? [String: Any],
        let settingsPreferences = settingsPlist[settingsPreferencesItems] as? [[String: Any]] else {
            return
    }

    var defaultsToRegister = [String: Any]()

    settingsPreferences.forEach { preference in
        if let key = preference[settingsPreferenceKey] as? String {
            defaultsToRegister[key] = preference[settingsPreferenceDefaultValue]
        }
    }

    UserDefaults.standard.register(defaults: defaultsToRegister)
}

So for a Root.plist like this:

root.plis

... the defaultsToRegister would be:

defaultsToRegister

There's also the new compactMapValues() API in Swift 5 that may or may not be helpful here.

backslash-f
  • 7,923
  • 7
  • 52
  • 80
3

Add notification for UserDefaults.didChangeNotification like below:

override func viewDidLoad() {
    super.viewDidLoad()
    registerSettingsBundle()
    NotificationCenter.default.addObserver(self, selector: #selector(updateDisplayFromDefaults), name: UserDefaults.didChangeNotification, object: nil)
    updateDisplayFromDefaults()
}
func registerSettingsBundle(){
    let appDefaults = [String:AnyObject]()
    UserDefaults.standard.register(defaults: appDefaults)
    UserDefaults.standard.synchronize()
}

func updateDisplayFromDefaults(){
    let defaults = UserDefaults.standard
    let update_lot = defaults.bool(forKey: "update_lot")
    print(update_lot)
}
Vini App
  • 7,339
  • 2
  • 26
  • 43
  • 1
    Please check https://stackoverflow.com/questions/9181544/setting-bundle-default-value-wont-set – Vini App Sep 28 '17 at 03:29
  • no need to force `synchronize()`. And whats the purpose of registering defaults with an empty dictionary? btw Swift native Dictionary value type since Swift3 is `Any` not `AnyObject`. And check the register signature `func register(defaults registrationDictionary: [String : Any])`. Also declaring `appDefaults` as constant it will be an empty dict forever – Leo Dabus Oct 03 '17 at 16:35
0

I am having a little bit of trouble following exactly what you're asking, but it sounds like you've created a settings bundle plist that specifies that some defaults are "true" if undefined (so that if a user hasn't set them they default to true). But I think when you do:

let update_lot = defaults.bool(forKey: "update_lot")

...there is no way for this code to know that "unset" should evaluate to "true" instead of "false". You could use defaults.object(forkey: "update_lot") instead and if that returns an object, get the boolean value of that object (or just call defaults.bool at that point), but if it returns nil assume that it's true.

Jeremy Brown
  • 156
  • 1
  • 4