Saving a Codable Struct to UserDefaults with Swift
Asked Answered
R

4

8

I am trying to encode a struct

struct Configuration : Encodable, Decodable {
    private enum CodingKeys : String, CodingKey {
        case title = "title"
        case contents = "contents"
    }
    var title : String?
    var contents: [[Int]]?
}

into JSON to store in a local key of UserDefaults.standard. I have the following code:

let jsonString = Configuration(title: nameField.text, contents: newContents)
let info = ["row" as String: jsonString as Configuration]
print("jsonString = \(jsonString)")
//trying to save object
let defaults = UserDefaults.standard
let recode = try! JSONEncoder().encode(jsonString)
defaults.set(recode, forKey: "simulationConfiguration")
//end of saving local

The print returns:

jsonString = Configuration(title: Optional("config"), contents: Optional([[4, 5], [5, 5], [6, 5]]))

so I believe I am creating the object correctly. However, when I try and retrieve the key the next time I run the simulator I get nothing. I put the following in AppDelegate and it always returns No Config.

let defaults = UserDefaults.standard
        let config = defaults.string(forKey: "simulationConfiguration") ?? "No Config"
        print("from app delegate = \(config.description)")

Any ideas? Thanks

Rehnberg answered 5/8, 2018 at 18:9 Comment(1)
May be someone will find this UserDefaultEncoded helpful: https://mcmap.net/q/239019/-save-struct-to-userdefaults It allows to save any Codable struct to UserDefaults.Fogarty
C
17

Here you are saving a Data value (which is correct)

defaults.set(recode, forKey: "simulationConfiguration")

But here you are reading a String

defaults.string(forKey: "simulationConfiguration")

You cannot save Data, read String and expect it to work.

Let's fix your code

First of all you don't need to manually specify the Coding Keys. So your struct become simply this

struct Configuration : Codable {
    var title : String?
    var contents: [[Int]]?
}

Saving

Now here's the code for saving it

let configuration = Configuration(title: "test title", contents: [[1, 2, 3]])
if let data = try? JSONEncoder().encode(configuration) {
    UserDefaults.standard.set(data, forKey: "simulationConfiguration")
}

Loading

And here's the code for reading it

if
    let data = UserDefaults.standard.value(forKey: "simulationConfiguration") as? Data,
    let configuration = try? JSONDecoder().decode(Configuration.self, from: data) {
    print(configuration)
}
Charioteer answered 5/8, 2018 at 18:18 Comment(1)
This did it! Thanks for the explanation.Rehnberg
A
0

encode(_:) function of JSONEncoder returns Data, not String. This means when you need to get the Configuration back from UserDefaults you need to get data and decode them. Here is example:

let defaults = UserDefaults.standard
guard let configData = defaults.data(forKey: "simulationConfiguration") else {
  return nil // here put something or change the control flow to if statement
}
return try? JSONDecoder().decode(Configuration.self, from: configData)

  • you also don't need to assign value to all the cases in CodingKeys, the values is automatically the name of the case
  • if you are conforming to both, Encodable and Decodable, you can simply use Codable instead as it is combination of both and defined as typealias Codable = Encodable & Decodable
Amelita answered 5/8, 2018 at 18:18 Comment(0)
G
0

If you want an external dependency that saves a boat load of frustration, checkout SwifterSwift

Here's how I did it in two lines using their UserDefaults extension.

For setting:

UserDefaults.standard.set(object: configuration, forKey: "configuration")

For retrieving the object:

guard let configuration = UserDefaults.standard.object(Configuration.self, with: "configuration") else { return }
print(configuration)

That's about it..!!

Genitive answered 1/4, 2019 at 2:33 Comment(1)
And you should only have to implement Configuration : Codable on your class.Genitive
T
0

Basically your UserDefault stored property will be look something like this,

private let userDefaults = UserDefaults.standard

var configuration: Configuration? {
    get {
        do {
            let data = userDefaults.data(forKey: "configuration_key")
            if let data {
                let config = try JSONDecoder().decode(User.self, from: data)
                return config
            }
        } catch let error {
            print("Preference \(#function) json decode error: \(error.localizedDescription)")
        }
        return nil
    } set {
        do {
            let data = try JSONEncoder().encode(newValue)
            userDefaults.set(data, forKey: "configuration_key")
        } catch let error {
            print("Preference \(#function) json encode error: \(error.localizedDescription)")
        }
    }
}
Tatting answered 28/12, 2022 at 6:51 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.