Reputation: 1131
I've been looking for the perfect UserDefaults wrapper that
My starting point was this StackOverflow answer, but it uses object:forKey
which doesn't work with custom objects (encoding URLs is always non-trivial for me).
My idea is to be able to use it like so:
struct Server: Identifiable, Codable, Equatable, Hashable { /* vars */ }
class ServerPickerViewModel: ObservableObject {
@Published(wrappedValue: Server.defaultServers.first!,
type: Server.self,
key: "currentServer")
var currentServer: Server?
}
To achieve this, I modified the code from @VictorKushnerov's answer:
import Combine
private var cancellables = [String: AnyCancellable]()
extension Published {
init<T: Encodable & Decodable>(wrappedValue defaultValue: T, type: T.Type, key: String) {
let decoder = JSONDecoder()
var value: T
if
let data = UserDefaults.standard.data(forKey: key),
let decodedVal = try? decoder.decode(T.self, from: data) {
value = decodedVal
} else {
value = defaultValue
}
self.init(initialValue: value) // <-- Error
cancellables[key] = projectedValue.sink { val in
let encoder = JSONEncoder()
let encodedVal = encoder.encode(val) // <-- Error
UserDefaults.standard.set(encodedVal, forKey: key)
}
}
}
There are currently two errors I can't get through, which are the following:
Cannot convert value of type 'T' to expected argument type 'Value'
it still relying on the underlying @Published
's Value
generic type, I wish I could override that with my type T.Instance method 'encode' requires that 'Published<Value>.Publisher.Output' (aka 'Value') conform to 'Encodable'
Upvotes: 2
Views: 206
Reputation: 52416
You can fix your compilation errors by using where Value : Codable
to restrict your extension. Then, you can get rid of your T
generic altogether (& you don't have to use the type
argument either):
extension Published where Value : Codable {
init(wrappedValue defaultValue: Value, key: String) {
let decoder = JSONDecoder()
var value: Value
if
let data = UserDefaults.standard.data(forKey: key),
let decodedVal = try? decoder.decode(Value.self, from: data) {
value = decodedVal
} else {
value = defaultValue
}
self.init(initialValue: value)
cancellables[key] = projectedValue.sink { val in
let encoder = JSONEncoder()
do {
let encodedVal = try encoder.encode(val)
UserDefaults.standard.set(encodedVal, forKey: key)
} catch {
print(error)
assertionFailure(error.localizedDescription)
}
}
}
}
This being said, I'd probably take the path instead of creating a custom property wrapper instead that wraps @AppStorage
instead of extending @Published
Upvotes: 1