Here's an example that demonstrates what I'm seeing:
struct Tapper: View {
@Binding var incrementMe: Int
var body: some View {
Button("Button With Binding") {
incrementMe += 1
}
}
}
struct ContentView: View {
@State private var tapCount: Int = 0 {
didSet{
print("didSet tapCount = \(tapCount)")
}
}
var body: some View {
VStack {
Text("tapCount: \(tapCount)")
Tapper(incrementMe: $tapCount)
.padding(4.0)
Button("Button Without Binding") {
tapCount += 1
}
}
.padding()
}
}
ContentView
renders this:
"Button With Binding" is the button from the Tapper
struct with the binding back to tapCount
. "Button Without Binding" is the button from ContentView
that increments tapCount
directly (no binding). As far as the view is concerned both buttons appear to work exactly as I would expect.
However, didSet
is only called for the "Button Without Binding" button. didSet
is never called for the "Button With Binding" button.
Clearly, tapCount
is being updated from "Button With Binding". When I tap it a few times and then tap "Button Without Binding" tapCount
increments from its previous value correctly.
Why isn't didSet
called from the binding? Without didSet
how can you confirm that a binding is indeed flowing all the way back to the source?
Since the View
struct is immutable you are really applying the didSet
to the property wrapper so it only works for that state wrapper and not any other wrappers like the binding one. So, you could just implement didSet
again for your @Binding
, e.g.
struct Tapper: View {
@Binding var incrementMe: Int {
didSet{
print("didSet incrementMe = \(incrementMe)")
}
}
var body: some View {
Button("Button With Binding") {
incrementMe += 1
}
}
}
To have the same code run in both didSet
s you could simply call a func or to have exactly the same code path just move the tapCount
into a struct, e.g.
struct Content {
var tapCount: Int = 0 {
didSet{
print("didSet tapCount = \(tapCount)")
}
}
// its nice to make funcs for testable logic
mutating func increment() {
tapCount += 1
}
}
struct Tapper: View {
@Binding var incrementMe: Int
var body: some View {
Button("Button With Binding") {
incrementMe += 1
}
}
}
struct ContentView: View {
@State private var content = Content()
var body: some View {
VStack {
Text("tapCount: \(content.tapCount)")
Tapper(incrementMe: $content.tapCount)
.padding(4.0)
Button("Button Without Binding") {
content.increment()
}
}
.padding()
}
}