Binding Viewmodel and Textfields with Swiftui

Binding ViewModel and TextFields with SwiftUI

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

SwiftUI View textfield not binding to ViewModel properties

You are using computed property which they are not supported until today in SwiftUI Views, try to use State, Binding or ObservableObject directly.



struct SignupEmailView: View {

@StateObject var signupViewModel = SignupViewModel()

var body: some View {

NavigationView {

VStack {

HStack {

Image(systemName: EMAIL_ICON)

TextField("enter your e-mail", text: $signupViewModel.email)
.autocapitalization(.none)

}
.modifier(DetailsTextFieldModifier())

NavigationLink("continue", destination: ConfirmCodeView())
.disabled(signupViewModel.email.isEmpty) // <<: Here

Spacer()

}
}
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
}
}


struct ConfirmCodeView: View {

@StateObject var signupNavigationViewModel = SignupNavigationViewModel()

var body: some View {

VStack {

TextField("Enter code sent to your email", text: $signupNavigationViewModel.emailCode)

NavigationLink("confirm", destination: SignupUsername())
.disabled(signupNavigationViewModel.emailCode.isEmpty) // <<: Here

Spacer()

}
.navigationBarBackButtonHidden(true)

}

}

SwiftUI ViewModel published property and binding

You were almost there. You just have to replace myViewModel.$text with $myViewModel.text.

class MyViewModel: ObservableObject {

var title: String = "SwiftUI"

@Published var text: String = ""
}

struct TextFieldView: View {

@ObservedObject var myViewModel: MyViewModel = MyViewModel()

var body: some View {
TextField(myViewModel.title, text: $myViewModel.text)
}
}

TextField expects a Binding (for text parameter) and ObservedObject property wrapper takes care of creating bindings to MyViewModel's properties using dynamic member lookup.

Swift and SwiftUI binding from TextField string to model's published, optional Integer property

SwiftUI's TextField has several Constructors. One of them takes a Formatter parameter.

https://developer.apple.com/documentation/swiftui/textfield

import SwiftUI

struct TextFieldFormatter: View {
@ObservedObject var model: Model = Model.shared

let formatter: NumberFormatter = {
let numFormatter = NumberFormatter()
numFormatter.numberStyle = .none
return numFormatter
}()

var body: some View {
VStack {
Text("State").onTapGesture {
self.endEditing()
}
TextField("int", value: $model.number, formatter: formatter).keyboardType(.numberPad)
Text("Echo: \(model.number)")
Text("Observable")
TextField("int", value: $model.number, formatter: formatter).keyboardType(.numberPad)
Text("Echo: \(model.number)")
}
}
}

struct TextFieldFormatter_Previews: PreviewProvider {
static var previews: some View {
TextFieldFormatter()
}
}

Also, if you are trying to disable the screen with the onTapGesture you might consider.

adding:

   @State var disabled: Bool = false

and changing:

       Form {
Text("Disable").onTapGesture {
//self.endEditing()
self.disabled = true
}
TextField("int", value: $model.number, formatter: formatter).keyboardType(.numberPad)
Text("Echo: \(model.number)")
Text("Observable")
TextField("int", value: $model.number, formatter: formatter).keyboardType(.numberPad)
Text("Echo: \(model.number)")
}.disabled($disabled.wrappedValue)

Giving feedback while the user is typing can be done a few different ways but a custom UITextView in a UIViewRepresentable class seems to be the most common.

Bidirectional binding with SwiftUI and Combine

If you want two way works, not only you need to publish, also you have to use binding for upward.

struct Person: Hashable {
var givenName: String
var familyName: String
}

// my person store - in the real app it's backed by coredata
class PersonStore: ObservableObject {
@Published var people: [Person] = [
Person(givenName: "Test",familyName: "Person")
]

static let shared = PersonStore()
}

// app entrypoint
struct PersonView: View {
@ObservedObject var viewModel: PersonView_ViewModel = PersonView_ViewModel()

var body: some View {
NavigationView {
VStack {
List(viewModel.people.indices, id: \.self) { idx in
NavigationLink(destination: PersonDetailView(viewModel: PersonDetailView_ViewModel(person: self.$viewModel.people , index: idx ))) {
Text(self.viewModel.people[idx].givenName)
}
}
}
}
}
}

class PersonView_ViewModel: ObservableObject {
@Published var people: [Person] = PersonStore.shared.people
}

// this is the detail view
struct PersonDetailView: View {
@ObservedObject var viewModel: PersonDetailView_ViewModel

var body: some View {
Form {
Section(header: Text("Parent View")) {
VStack {
TextField("Given Name", text: self.viewModel.person.givenName)
Divider()
TextField("Family Name", text: self.viewModel.person.familyName)
}
}
PersonBasicDetails(viewModel: PersonBasicDetails_ViewModel(person: viewModel.person))
}
}
}

// viewmodel associated with detail view
class PersonDetailView_ViewModel: ObservableObject {
@Published var person: Binding<Person>

init(person: Binding<[Person]> ,index: Int) {
self.person = person[index]
}
}

// this is the child view - in the real app there are multiple sections which are conditionally rendered
struct PersonBasicDetails: View {
@ObservedObject var viewModel: PersonBasicDetails_ViewModel

var body: some View {
Section(header: Text("Child View")) {
VStack {
TextField("Given Name", text: self.viewModel.person.givenName)
Divider()
TextField("Family Name", text: self.viewModel.person.familyName)
}
}
}
}

class PersonBasicDetails_ViewModel: ObservableObject {
@Published var person: Binding<Person>

init(person: Binding<Person>) {
self.person = person //person
}
}

struct PersonView_Previews: PreviewProvider {
static var previews: some View {
PersonView()
}
}


Related Topics



Leave a reply



Submit