Reputation: 12915
Normally we can use didSet
in swift to monitor the updates of a variable. But it didn't work for a @Binding
variable. For example, I have the following code:
@Binding var text {
didSet {
......
}
}
But the didSet
is never been called.Any idea? Thanks.
Upvotes: 33
Views: 14259
Reputation: 54426
Use onChange
with a zero or two-parameter closure:
struct ContentView: View {
@Binding var counter: Int
var body: some View {
Text(counter, format: .number)
.onChange(of: counter) {
print("onChange")
}
.onChange(of: counter) { oldValue, newValue in
print("onChange: \(oldValue), \(newValue)")
}
}
}
Use onChange
with a one-parameter closure:
struct ContentView: View {
@Binding var counter: Int
var body: some View {
Text(String(counter))
.onChange(of: counter) { value in
print("onChange: \(value)")
}
}
}
Use onReceive
- a universal solution for all SwiftUI versions:
struct ContentView: View {
@Binding var counter: Int
var body: some View {
Text(String(counter))
.onReceive(Just(counter)) { value in
print("onReceive: \(value)")
}
}
}
Upvotes: 30
Reputation: 1394
The best way is to wrap the property in an ObservableObject
:
final class TextStore: ObservableObject {
@Published var text: String = "" {
didSet { ... }
}
}
And then use that ObservableObject
's property as a binding variable in your view:
struct ContentView: View {
@ObservedObject var store = TextStore()
var body: some View {
TextField("", text: $store.text)
}
}
didSet
will now be called whenever text
changes.
Alternatively, you could create a sort of makeshift Binding
value:
TextField("", text: Binding<String>(
get: {
return self.text
},
set: { newValue in
self.text = newValue
...
}
))
Just note that with this second strategy, the get
function will be called every time the view is updated. I wouldn't recommend using this approach, but nevertheless it's good to be aware of it.
Upvotes: 0
Reputation: 385500
You shouldn’t need a didSet
observer on a @Binding
.
If you want a didSet
because you want to compute something else for display when text
changes, just compute it. For example, if you want to display the count of characters in text
:
struct ContentView: View {
@Binding var text: String
var count: Int { text.count }
var body: some View {
VStack {
Text(text)
Text(“count: \(count)”)
}
}
}
If you want to observe text
because you want to make some other change to your data model, then observing the change from your View
is wrong. You should be observing the change from elsewhere in your model, or in a controller object, not from your View
. Remember that your View
is a value type, not a reference type. SwiftUI creates it when needed, and might store multiple copies of it, or no copies at all.
Upvotes: 13