12

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)

1 Answer 1

27

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)

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for the suggestion. onChange has the added benefit of only reacting when the value changed, as opposed to onReceive that also fires when the view loads
took me forever to find this to support ios13, a perfect onChange replacement, thanks :)
It just gets called on every refresh of the view, doesn't it?
Yes it is, is stack in the loop
For me .onReceive(Just(binding)) works but .onChange(of: binding) does not.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.