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
