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")
}
}
}