paulz
paulz

Reputation: 313

SwiftUI how do I create a separate FormField for better code separation?

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

Answers (1)

Chen-Hai Teng
Chen-Hai Teng

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

Related Questions