'SwiftUI/Combine: subscribe to value change of @Binding

I have a view with a view model, and actions in this view can change the view model. To be able to break out logic into reusable pieces, I have part of the view as its own view, with a @Binding to the values it needs to have. Now, I want to be able to perform some logic based on the value changes, not necessarily only view changes. How can I do that? If it was a regular property, I'd implement a didSet, but that gets me nowhere. I wanted to use Combine to and treat the @Binding as a publisher, but I couldn't find a way to do that either. Suggestions?

Here's the code:

class ViewModel: ObservableObject {
    @Published var counter: Int = 0
}

struct Greeter: View {
    @Binding var counter: Int {
        didSet {
            // this isn't printed....
            print("Did set -> \(counter)")
        }
    }
    
    init(counter: Binding<Int>) {
        self._counter = counter
        
        // ...so how about setting up a subscription to the @Binding counter above here?
    }
    
    var body: some View {
        Text("Hello, world #\(counter)!")
            .padding()
    }
}

struct ContentView: View {
    
    @ObservedObject var viewModel: ViewModel
    
    var body: some View {
        VStack {
            Greeter(counter: $viewModel.counter)
            Button("Go!") {
                viewModel.counter += 1
            }
        }
    }
}

So I want to retain the structure where the data is in a ViewModel, and that only parts of it is being passed down to the subview. And it is in the subview (Greeter) I want to be able to do something (let's say print the value like in the didSet)



Solution 1:[1]

Here is possible approach. Tested with Xcode 11.4 / iOS 13.4 (SwiftUI 1.0+)

struct Greeter: View {
    @Binding var counter: Int

    var body: some View {
        Text("Hello, world #\(counter)!")
            .padding()
            .onReceive(Just(counter)) {    // << subscribe
                print(">> greeter: \($0)") // << use !!
            }
    }
}

Note for SwiftUI 2.0+: you can use .onChange(of: counter) { instead (which is actually built-in replacement for above)

backup

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1