I want a variable to be an EnvironmentObject and I also want it to be persisted, so that it's the same every time that I relaunch my app.
To achieve that, I have already created the following propertyWrapper:
import Foundation
@propertyWrapper
struct UserDefault<T: Codable> {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
if let encodedValue = UserDefaults.standard.object(forKey: key) as? Data {
let decoder = JSONDecoder()
let decodedValue = try! decoder.decode(T.self, from: encodedValue)
return decodedValue
} else {
return defaultValue
}
} set {
let encoder = JSONEncoder()
let encodedValue = try! encoder.encode(newValue)
UserDefaults.standard.set(encodedValue, forKey: key)
}
}
}
But already having a property wrapper means that I can't use the @Published property wrapper from Combine. (Using two property wrappers on one variable doesn't sound like a good idea, and I haven't found a way to get that working.)
I solved that problem by making a custom objectWillChange let constant and calling its .send(input:) method in willSet for every variable.
So this is my DataStore class:
import SwiftUI
import Combine
final class DataStore: ObservableObject {
let objectWillChange = PassthroughSubject<DataStore, Never>()
@UserDefault(key: "the text", defaultValue: "Hello world!")
var text: String {
willSet {
objectWillChange.send(self)
}
}
}
And this is my View:
struct StartView : View {
@EnvironmentObject var dataStore: DataStore
var body: some View {
VStack {
TextField("Enter text", text: $dataStore.text)
Button("Reset text", action: {
self.dataStore.text = "Hello World!"
})
}
}
}
But somehow I really believe that there should be a more beautiful way than making a custom objectWillChange. Is there a way to make a single property wrapper that covers both the persisting and the "publishing"? Or should I do something completely different, to reach my goal?
Thanks!