Given a obj-c keypath
@objc dynamic var someProp: String { string(forKey: "someProp") }
A regular publisher:
private let sub = UserDefaults.standard.publisher(for: \.someProp).sink { print($0) }
This publishes only works for the first value (e.g. the current value).
However observing the sub
publisher from SwiftUI works fine:
.onReceive(pub) { value in
print("received", value)
}
This publishes any subsequent updates.
Any ideas why the former doesn't work?
Edit: Here is a minimal reproducible example:
public extension UserDefaults {
@objc dynamic var value1: Int {
integer(forKey: "string1")
}
}
struct ContentView: View {
@StateObject var vm = ViewModel()
private let pub = UserDefaults.standard.publisher(for: \.value1)
var body: some View {
Button("Add") {
var value = UserDefaults.standard.value(forKey: "value1") as? Int ?? 0
value = 1
debugPrint("SET", value)
UserDefaults.standard.set(value, forKey: "value1")
}
.onReceive(pub) { value in
debugPrint("UI", value)
}
}
class ViewModel: ObservableObject {
private let sub = UserDefaults.standard.publisher(for: \.value1).sink {
debugPrint("SUB", $0)
}
}
}
CodePudding user response:
The error here is how you access and assign your values in the Button action. You are setting the values for the key value1
. But the publisher observes the key string1
with the dynamic var named value1
.
TLDR: You confused the dynamic var with your key
I would recommend you ommit the access via .value(forKey: "")
and use only your dynamic var.
public extension UserDefaults {
@objc dynamic var value1: Int {
// add getter and setter
get{
integer(forKey: "string1")
}
set{
set(newValue, forKey: "string1")
}
}
}
struct ContentView: View {
@StateObject var vm = ViewModel()
private let pub = UserDefaults.standard.publisher(for: \.value1)
var body: some View {
Button("Add") {
//here
UserDefaults.standard.value1 = 1
debugPrint("SET", UserDefaults.standard.value1)
}
.onReceive(pub) { value in
debugPrint("UI", value)
}
}
class ViewModel: ObservableObject {
private let sub = UserDefaults.standard.publisher(for: \.value1).sink {
debugPrint("SUB", $0)
}
}
}
Prints:
"SUB" 0
"UI" 0
"SET" 1
"SUB" 1
"UI" 1
"SET" 2
"SUB" 2
"UI" 2
"SET" 3
"SUB" 3
"UI" 3