Issue #714
Below is an example of a parent ContentView with State and a child Sidebar with a Binding.
The didSet is only called for the property that is changed.
When we click Button in ContentView, that changes State property, so only the didSet in ContentView is called
When we click Button in Sidebar, that changes Binding property, so only the didSet in Sidebar is called
enum Tag: String {
case all
case settings
}
struct ContentView: View {
@State
private var tag: Tag = .all {
didSet {
print("ContentView \(tag)")
}
}
var body: some View {
Sidebar(tag: $tag)
Button(action: { tag = .settings }) {
Text("Button in ContentView")
}
}
}
struct Sidebar: View {
@Binding
var tag: Tag {
didSet {
print("Sidebar \(tag)")
}
}
var body: some View {
Text(tag.rawValue)
Button(action: { tag = .settings }) {
Text("Button in Sidebar")
}
}
}
Custom Binding with get set
Another way to observe Binding changes is to use custom Binding with get, set. Here even if we click Button in ContentView, the set block is triggered and here we can change State
var body: some View {
Sidebar(tag: Binding<Tag>(
get: { tag },
set: { newTag in
self.tag = newTag
print("ContentView newTag \(newTag)")
}
))
Button(action: { tag = .settings }) {
Text("Button in ContentView")
}
}
Convenient new Binding
We can also make convenient extension on Binding to return new Binding, with a hook allowing us to inspect newValue. So we can call like Sidebar(tag: $tag.didSet
extension Binding {
func didSet(_ didSet: @escaping (Value) -> Void) -> Binding<Value> {
Binding(
get: { wrappedValue },
set: { newValue in
self.wrappedValue = newValue
didSet(newValue)
}
)
}
}
struct ContentView: View {
@State
private var tag: Tag = .all {
didSet {
print("ContentView \(tag)")
}
}
var body: some View {
Sidebar(tag: $tag.didSet { newValue in
print("ContentView newTag \(newValue)")
})
Button(action: { tag = .settings }) {
Text("Button in ContentView")
}
}
}
Start the conversation