Skip to content

FormCraftGroup Protocol

WARNING

FormCraftGroup must be declared inside a struct annotated with @FormCraft.

Use it when you want to split a large form into logical nested sections while keeping typed access.

Example:

swift
@FormCraft
private struct CheckoutFields: FormCraftFields {
    struct Customer: FormCraftGroup {
        var firstName = FormCraftField(value: "") { value in
            await FormCraftValidationRules()
                .string()
                .notEmpty()
                .min(min: 2)
                .validate(value: value)
        }

        var email = FormCraftField(value: "") { value in
            await FormCraftValidationRules()
                .string()
                .notEmpty()
                .email()
                .validate(value: value)
        }
    }

    struct Delivery: FormCraftGroup {
        var city = FormCraftField(value: "") { value in
            await FormCraftValidationRules()
                .string()
                .notEmpty()
                .validate(value: value)
        }

        var street = FormCraftField(value: "") { value in
            await FormCraftValidationRules()
                .string()
                .notEmpty()
                .min(min: 5)
                .validate(value: value)
        }

        var zipCode = FormCraftField(value: "") { value in
            await FormCraftValidationRules()
                .string()
                .notEmpty()
                .regex(pattern: #/^\d{5}$/#)
                .validate(value: value)
        }
    }

    struct Payment: FormCraftGroup {
        var cardHolder = FormCraftField(value: "") { value in
            await FormCraftValidationRules()
                .string()
                .notEmpty()
                .validate(value: value)
        }

        var cardLast4 = FormCraftField(value: "") { value in
            await FormCraftValidationRules()
                .string()
                .regex(pattern: #/^\d{4}$/#)
                .validate(value: value)
        }
    }

    var customer = Customer()
    var delivery = Delivery()
    var payment = Payment()
}

With validated submit data you can access nested values:

swift
form.handleSubmit { data in
    print("Customer:", data.customer.firstName, data.customer.email)
    print("Delivery:", data.delivery.city, data.delivery.street, data.delivery.zipCode)
    print("Payment:", data.payment.cardHolder, data.payment.cardLast4)
}

UI binding also uses nested key paths. In FormCraftControllerView, key points to nested fields the same way:

swift
import SwiftUI

struct CheckoutView: View {
    @State private var form = FormCraft(fields: CheckoutFields())

    var body: some View {
        VStack(spacing: 12) {
            FormCraftView(formConfig: form) {
                // Customer group
                FormCraftControllerView(formConfig: form, key: \.customer.firstName) { value, field in
                    TextField("First Name", text: value)
                    if let firstError = field.errors?.messages.first {
                        Text(firstError).foregroundStyle(.red)
                    }
                }

                FormCraftControllerView(formConfig: form, key: \.customer.email) { value, field in
                    TextField("Email", text: value)
                    if let firstError = field.errors?.messages.first {
                        Text(firstError).foregroundStyle(.red)
                    }
                }

                // Delivery group
                FormCraftControllerView(formConfig: form, key: \.delivery.city) { value, _ in
                    TextField("City", text: value)
                }

                FormCraftControllerView(formConfig: form, key: \.delivery.street) { value, _ in
                    TextField("Street", text: value)
                }

                FormCraftControllerView(formConfig: form, key: \.delivery.zipCode) { value, _ in
                    TextField("ZIP Code", text: value)
                }

                // Payment group
                FormCraftControllerView(formConfig: form, key: \.payment.cardHolder) { value, _ in
                    TextField("Card Holder", text: value)
                }

                FormCraftControllerView(formConfig: form, key: \.payment.cardLast4) { value, _ in
                    TextField("Card Last 4", text: value)
                }
            }

            Button(
                "Submit",
                action: form.handleSubmit { data in
                    print("Customer:", data.customer.firstName, data.customer.email)
                    print("Delivery:", data.delivery.city, data.delivery.street, data.delivery.zipCode)
                    print("Payment:", data.payment.cardHolder, data.payment.cardLast4)
                }
            )
        }
        .padding()
    }
}

Released under the MIT License.