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