Reputation: 313
I am new to SwiftUI and building a couple of forms. They share some input fields (title Text, input TextField, and error Text). How can I make each FormField a separate View, which interact with Submit button?
The code looks like below. How can I make a FormView(title: String, event: (Some event type?)) so that I can reuse name form field in different forms?
import SwiftUI
struct Form1: View {
@State private var name: String = ""
@State private var phone: String = ""
@State private var nameErr: String = ""
@State private var phoneErr: String = ""
func validate(val: String, title: String) -> String {
return val.count == 0 ? "\(title) cannot be empty" : ""
}
var body: some View {
VStack(alignment: .leading) {
Text("Name")
TextField("Input name", text: $name)
Text(nameErr).foregroundColor(.red)
Divider()
Text("Phone")
TextField("Input phone", text: $phone)
Text(phoneErr).foregroundColor(.red)
Spacer()
Button("Submit", action: {
nameErr = name.count == 0 ? "Name cannot be empty" : ""
phoneErr = phone.count == 0 ? "Phone number cannot be empty" : ""
})
}
}
}
In form, I don't want to see nameErr and phoneErr. They need to be incapsulated further down.
@State private var name: String = ""
@State private var phone: String = ""
VStack(alignment: .leading) {
FormField(title: "Name", val: $name, validator: notEmpty)
Divider()
FormField(title: "Phone", val: $phone, validator: notEmpty)
Spacer()
Button("Submit", action: {
// What do I put here?
})
}
Upvotes: 0
Views: 444
Reputation: 753
Following are simple code that might help you:
public struct FormView: View {
@State var title: String
// It might be the problem of original source,
// if you want to deliver some value, use @Binding
@Binding var field: String
// Bind validation result for outer use.
@Binding var valid: Bool
// Also, I think you might need a placeholder
@State private var placeholder: String = ""
@State private var showError: Bool = false
@State private var errMsg = ""
//Also, you can use closure to instead of it.
func validation(str: String) {
// write your validating logic
if(str.count > 4) {
showError = true
// Also, can add error handling here
errMsg = "field too long"
} else {
showError = false
// I would suggest use Error to instead of errMsg
errMsg = ""
}
}
// Update, add default argument for valid.
public init(title: String, field: Binding<String>, valid: Binding<Bool> = .constant(false)) {
self.title = title
self.placeholder = "Input \(title.lowercased()) here."
self._field = field
self._valid = valid
}
public var body: some View {
VStack(alignment: .leading) {
Text(title)
TextField(placeholder, text: $field) { editing in
//Call validation depends on editing status
if !editing {
validation(str: field)
}
} onCommit: {
//Call validation when user pefrom action such as return key.
validation(str: field)
}
if showError {
Text(errMsg).foregroundColor(.red)
}
}.padding(5).border(Color.blue, width: 2)
}
}
Now you can use FormView as :
struct Preview: View {
@State private var name = ""
@State private var nameIsValid = false
@State private var phone = ""
@State private var phoneIsValid = false
var body: some View {
VStack {
FormView(title: "Name", field: $name, valid: $nameIsValid)
Divider()
FormView(title: "Phone", field: $phone, valid: $phoneIsValid)
Spacer()
Button("Submit") {
// Check valid status and do what you want.
if nameIsValid & phoneIsValid {
// Do something
}
}
}.frame(width: 200, alignment: .center)
}
}
It's simple, but I think you can modify it to fit your necessary.
Update: If you want validate in submit:
struct Preview: View {
@State private var name = ""
@State private var nameIsValid = false
@State private var phone = ""
@State private var phoneIsValid = false
var body: some View {
VStack {
FormView(title: "Name", field: $name)
Divider()
FormView(title: "Phone", field: $phone)
Spacer()
Button("Submit") {
// validate field here.
// validater.validate(name)
// validater.validate(phone)
}
}.frame(width: 200, alignment: .center)
}
}
Upvotes: 1