0

Apologies for the stupid question, I'm still really new to the Swift language.

Following up on this answer by @matt, I want to combine these two statements into a single var

UserDefaults.standard.set(try? PropertyListEncoder().encode(songs), forKey:"songs")
if let data = UserDefaults.standard.value(forKey:"songs") as? Data {
    let songs2 = try? PropertyListDecoder().decode(Array<Song>.self, from: data)
}

I've thought maybe using a var with didSet {} like something along the lines of

var test: Array = UserDefaults.standard. { // ??
    didSet {
        UserDefaults.standard.set(try? PropertyListEncoder().encode(test), forKey: "songs")
    }
}

But I can't think of where to go from here. Thanks for the help in advance :))

Lemon
  • 1,184
  • 11
  • 33

2 Answers2

2

The property should not be a stored property. It should be a computed property, with get and set accessors:

var songsFromUserDefaults: [Song]? {
    get {
        if let data = UserDefaults.standard.value(forKey:"songs") as? Data {
            return try? PropertyListDecoder().decode(Array<Song>.self, from: data)
        } else {
            return nil
        }
    }

    set {
        if let val = newValue {
            UserDefaults.standard.set(try? PropertyListEncoder().encode(val), forKey:"songs")
        }
    }
}

Notice that since the decoding can fail, the getter returns an optional. This forces the setter to accept an optional newValue, and I have decided to only update UserDefaults when the value is not nil. Another design is to use try! when decoding, or return an empty array when the decoding fails. This way the type of the property can be non-optional, and the nil-check in the setter can be removed.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
2

While you can use computed properties like Sweeper suggested (+1), I might consider putting this logic in a property wrapper.

In SwiftUI you can use AppStorage. Or you can roll your own. Here is a simplified example:

@propertyWrapper public struct Saved<Value: Codable> {
    private let key: String

    public var wrappedValue: Value? {
        get {
            guard let data = UserDefaults.standard.data(forKey: key) else { return nil }
            return (try? JSONDecoder().decode(Value.self, from: data))
        }

        set {
            guard
                let value = newValue,
                let data = try? JSONEncoder().encode(value)
            else {
                UserDefaults.standard.removeObject(forKey: key)
                return
            }
            UserDefaults.standard.set(data, forKey: key)
        }
    }

    init(key: String) {
        self.key = key
    }
}

And then you can do things like:

@Saved(key: "username") var username: String?

Or

@Saved(key: "songs") var songs: [Song]?
Rob
  • 415,655
  • 72
  • 787
  • 1,044