Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 4 Settings Bundle, get defaults

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.

like image 774
user979331 Avatar asked Sep 27 '17 17:09

user979331


2 Answers

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.

like image 198
backslash-f Avatar answered Nov 16 '22 00:11

backslash-f


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
}
like image 39
Kamil Szostakowski Avatar answered Nov 16 '22 00:11

Kamil Szostakowski