From what I've read, @AppStorage vars should be @Published, however the following code generates a syntax error at extended
's .sink
modifier: Cannot call value of non-function type 'Binding<Subject>'
class LanguageManager: ObservableObject {
@Published var fred = "Fred"
@AppStorage("extended") var extended: Bool = true
private var subscriptions = Set<AnyCancellable>()
init() {
$fred
.sink(receiveValue: {value in
print("value: \(value)")
})
.store(in: &subscriptions)
$extended
.sink(receiveValue: {value in
print("value: \(value)")
})
.store(in: &subscriptions)
}
Does anyone know of a way to listen for (subscribe to) changes in @AppStorage values?
didSet
works in for a specific subset of value changes, but this is not sufficient for my intended use.
I found an implementation I'm mostly happy with. First, I defined a Notification.Name and created a userDefaults binding 'factory'
extension Notification.Name {
static let userDefaultsChanged = Notification.Name(rawValue: "user.defaults.changed")
}
struct BindingFactory {
static func binding(for defaultsKey: String) -> Binding<Bool> {
return Binding {
return UserDefaults.standard.bool(forKey: defaultsKey)
} set: { newValue in
UserDefaults.standard.setValue(newValue, forKey: defaultsKey)
NotificationCenter.default.post(name: .userDefaultsChanged, object: defaultsKey)
}
}
}
Then in my UI elements that were previously using @AppStorage, they now use the new bindings.
var body: some View {
VStack {
Toggle("Extended", isOn: BindingFactory.binding(for: "extended"))
Text(LanguageManager.shared.summary)
}
}
And now LanguageManager's init can add a subscriber to the userDefaultsChanged
notification.
init() {
NotificationCenter.default.publisher(for: .userDefaultsChanged)
.sink(receiveValue: { notification in
print("\(notification.object!) changed")
self.updateItems()
})
.store(in: &subscriptions)
}
The main thing I don't really like about this is the need to create a static func binding(for defaultsKey: String) -> Binding<Bool>
for each type of binding (Bool, String, Int, etc.)