0

I'm wondering what's the best way to store user settings in Swift. With user settings I mean simple (small) data, not some big files. Until now, I used a class with all properties I wanted to be saved.

All of those properties conform to Codable. However, I didn't save the whole class in UserDefaults, instead I saved each property individually. But I don't like that approach. There are several issues with it: The code gets longer because for every variable I have to write didSet{...}. For example:

var percentage: Double = UserDefaults.standard.double(forKey: "percentage")  {
        didSet {
            UserDefaults.standard.set(percentage, forKey: "percentage")
        }
    }

As you can see, the variable name is written 4 times here. So there is a high chance of misspelling / copy and paste errors.

So why don't I save the whole class then? Well, I noticed that if I add a variable to the class, the decoding of the class doesn't work anymore and all data is lost even if I give the new variable a default value.

There seems to be a way to fix this: decoding manually. For example:

 required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        id = try container.decode(UUID.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
        //etc...
    }

However, decoding manually seems to require me to decode every variable separately. I don't like that as well because there is also a high chance to forget about one variable etc. (so it's the same problem as above).

What I would like to do as well is to give the user the option to export and import settings and to use iCloud for settings synchronization. For the former it would be better to store the whole Settings class (I could export and import the JSON file).

Is there a smart way to do this?

Thanks for helping me out!

leonboe1
  • 1,004
  • 9
  • 27
  • _"if I add a variable to the class, the decoding of the class doesn't work anymore "_, this sounds really weird and also the stuff about decoding properties individually. If you have a type with properties that all conforms to Codable then the type conforms to Codable. Maybe you can find a solution in [this](https://stackoverflow.com/questions/56822195/how-do-i-use-userdefaults-with-swiftui) question or [this](https://stackoverflow.com/questions/44495842/how-to-save-custom-objects-that-implements-codable) – Joakim Danielson Oct 14 '20 at 12:30
  • What I meant with the former that is if the data of the app gets encoded and I update the app adding a new variable, then pushing the update to the device the decoding will fail and all data will be lost. – leonboe1 Oct 14 '20 at 12:34
  • 1
    Ok, you meant if you add a new property to the class. I guess that is a problem you will have to deal with in one way or another for most solutions – Joakim Danielson Oct 14 '20 at 12:38
  • 1
    I always try to avoid raw string values because that may cause misspelling as you noticed so I recommend using an enum contains your string values, that would be much better and efficient. @leonboe1 – Essam Fahmi Oct 14 '20 at 12:42

1 Answers1

1

You might also want some kind of class managing all of your user's stuff, something like this:

class SettingsManager {
    
    private let defaults = UserDefaults.standard
    
    var percentage: Double {
        get { return defaults.value(forKey: "percentage") as? Double ?? 0.0 }
        set { defaults.set(newValue, forKey: "percentage") }
    }
    
}

This way you can reduce the required amount of code to this:

// Retrieve a value
let percentage = SettingsManager().percentage
// Set new value
SettingsManager().percentage = 0.55

Ideally you might use property wrappers like described here. This eliminates the need of encoding/decoding the value until it's a custom type.

Alexandr Chekel
  • 382
  • 2
  • 15