1

As far as I understand, if I need to update the view model from inside a view, I need to make it a binding variable.

The model.

enum Options: Int, Identifiable, Equatable, CaseIterable {
    case option1 = 1
    case option2 = 2
    case option3 = 3
    case option4 = 4
    
    var id: String { "\(self.rawValue)" }
}

class TestViewModel:  ObservableObject {
    var selectedOption = Options.option1
    ...
}

The view.


struct TestView: View {
    @Binding var viewModel: TestViewModel
    @State var selectedOption = Options.option1

    var body: some View {
        
        Picker("Option 1", selection: $viewModel.selectedOption) {
            ForEach(Options.allCases, id: \.id) { value in
                Text(value.id)
                    .tag(value)
            }
        }
        Text("Selected Option: \(viewModel.selectedOption.rawValue)")
        
        Picker("Option 2", selection: $selectedOption) {
            ForEach(Options.allCases, id: \.id) { value in
                Text(value.id)
                    .tag(value)
            }
        }
        Text("Selected Option: \(selectedOption.rawValue)")
    }
        
}

  • Selecting a value in the first picket, the view doesn't refresh.
  • Selecting a value in the second picker, the view refreshes (as expected)

How can I make the view refresh using @Binding which is required to update the model?

I come up with this solution which works but it doesn't look good to me.

...
 let b = Binding<Options>(
            get: {
                viewModel.selectedOption
            },
            set: {
                viewModel.selectedOption = $0
                selectedOption = $0 // << this forces the view to refresh
            }
        )

 Picker("Speed 1", selection: b) {
            ForEach(Options.allCases, id: \.id) { value in
                Text(value.id)
                    .tag(value)
            }
        }
...
1
  • Binding does not refresh a view, view is refreshed by changed state, to which binding has bound, but not always - only when body of the view depends on that state. Commented Nov 5, 2020 at 17:58

2 Answers 2

2

The ObservableObject works in pair with ObservedObject wrapper. In such case view model becomes source of truth for a view. So...

  1. make property published
class TestViewModel:  ObservableObject {
    @Published var selectedOption = Options.option1     // << here !!
    ...
}
  1. make view model wrapped by ObservedObject
struct TestView: View {
    @ObservedObject var viewModel: TestViewModel
  1. bind picker directly to view model
 Picker("Speed 1", selection: $viewModel.selectedOption) {
            ForEach(Options.allCases, id: \.id) { value in
                Text(value.id)
                    .tag(value)
            }
        }
Sign up to request clarification or add additional context in comments.

Comments

0

I am not sure if you have a valid reason to use ObservableObject here but you can easily achieve the same results by using a struct and State variable:

struct TestViewModel {

    var selectedOption = Options.option1
}

struct TestView: View {

    @State var viewModel: TestViewModel

    var body: some View {
        Picker("Option 1", selection: $viewModel.selectedOption) {
            ForEach(Options.allCases, id: \.id) { value in
                Text(value.id)
                    .tag(value)
            }
        }
    }
}

Comments

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.