Fab
Fab

Reputation: 1564

How to make a @Binding variable refresh the view

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

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

Upvotes: 1

Views: 70

Answers (2)

Cuneyt
Cuneyt

Reputation: 981

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

Upvotes: 0

Asperi
Asperi

Reputation: 257493

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

Upvotes: 2

Related Questions