21

I'm looking for the best way to create a bind between textfields and ViewModel. At the moment I'm creating a @State for each textfield and I'm manually sending the value from textfield to the viewModel properties when needed. I'm pretty sure this is not the best way to go... Is there a way to bind the TextField with the ViewModel property?

This is my current code:

struct SigninView: View {
    @State var username:String = ""
    @State var password:String = ""

    var viewModel:SignInViewModel

    var body: some View {
        VStack(alignment: .leading, spacing: 15.0){

            DefaultTextField(placeholder: "username", value: $username)
            DefaultTextField(placeholder: "password", value: $password)

            Spacer()

            FillButton(title:"Sign In"){
                ///// IS THIS CORRECT?
                self.viewModel.email = self.username
                self.viewModel.password = self.password
                //------------------
                self.viewModel.signin()
            }
        }.padding()
    }
}

The view model is something like:

class SignInViewModel:ObservableObject{

    var username:String? = nil
    var password:String? = nil
MatterGoal
  • 16,038
  • 19
  • 109
  • 186

2 Answers2

39

I think we can simplify it with below code.

class SignInViewModel: ObservableObject{
    @Published var username = ""
    @Published var password = ""
}


struct SigninView: View {
    @ObservedObject var viewModel = SignInViewModel()

    var body: some View {
        VStack(alignment: .leading, spacing: 15.0){

            TextField("username", text: $viewModel.username)
            TextField("password", text: $viewModel.password)

            Spacer()

            Button("Sign In"){
                print("User: \(self.viewModel.username)")
                print("Pass: \(self.viewModel.password)")
            }
        }.padding()
    }
}
Satyam
  • 15,493
  • 31
  • 131
  • 244
  • 3
    won't this cause an additional view update due to the viewmodel being observed and its published members changing from the view? – giorashc Jan 27 '21 at 11:03
  • @giorashc the concept of observer itself here is to update the UI when the view model changes or get the text in the text fields into a variable when the user types. – Satyam Mar 22 '21 at 04:15
20

Here is possible approach (of course I don't have your custom views, so just replaced with standard, but the idea is the same). Tested with Xcode 11.2/iOS 13.2

class SignInViewModel: ObservableObject{

    @Published var username:String? = nil
    @Published var password:String? = nil
}


struct SigninView: View {
    @ObservedObject var viewModel = SignInViewModel()

    var body: some View {
        VStack(alignment: .leading, spacing: 15.0){

            TextField("username", text: Binding<String>(
                get: {self.viewModel.username ?? ""},
                set: {self.viewModel.username = $0}))
            TextField("password", text: Binding<String>(
                get: {self.viewModel.password ?? ""},
                set: {self.viewModel.password = $0}))

            Spacer()

            Button("Sign In"){
                print("User: \(self.viewModel.username)")
                print("Pass: \(self.viewModel.password)")
            }
        }.padding()
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690