Skip to content
All essays
iOSMarch 27, 202513 min

SwiftUI Forms Complete Guide: Input and Validation

Create effective forms in SwiftUI. Text fields, validation, and form submission.

Ü
Ümit Uz
Mobile & Full Stack Developer

Forms are essential for user input in iOS apps. Learn to create intuitive, validated forms in SwiftUI.

Basic Form Structure

Simple Form

swift
import SwiftUI

struct BasicForm: View {
    @State private var name: String = ""
    @State private var email: String = ""
    @State private var message: String = ""

    var body: some View {
        Form {
            Section(header: Text("Personal Information")) {
                TextField("Name", text: $name)
                TextField("Email", text: $email)
                    .keyboardType(.emailAddress)
                    .autocapitalization(.none)
            }

            Section(header: Text("Message")) {
                TextEditor(text: $message)
                    .frame(minHeight: 100)
            }

            Section {
                Button("Submit") {
                    submitForm()
                }
            }
        }
        .navigationTitle("Contact Form")
    }

    private func submitForm() {
        print("Name: \(name)")
        print("Email: \(email)")
        print("Message: \(message)")
    }
}

Form Validation

Validate Input

swift
struct ValidatedForm: View {
    @State private var name: String = ""
    @State private var email: String = ""
    @State private var password: String = ""
    @State private var errorMessage: String?

    var isValid: Bool {
        !name.isEmpty &&
        isValidEmail(email) &&
        password.count >= 8
    }

    var body: some View {
        Form {
            Section {
                TextField("Name", text: $name)
                TextField("Email", text: $email)
                    .keyboardType(.emailAddress)
                    .autocapitalization(.none)

                SecureField("Password", text: $password)
            } header: {
                Text("Account Information")
            } footer: {
                if let error = errorMessage {
                    Text(error)
                        .foregroundColor(.red)
                }
            }

            Section {
                Button("Create Account") {
                    createAccount()
                }
                .disabled(!isValid)
            }
        }
        .navigationTitle("Sign Up")
    }

    private func isValidEmail(_ email: String) -> Bool {
        let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let predicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
        return predicate.evaluate(with: email)
    }

    private func createAccount() {
        guard isValid else {
            errorMessage = "Please fill all fields correctly"
            return
        }

        errorMessage = nil
        // Create account logic
    }
}

Custom Input Views

Picker

swift
struct PickerForm: View {
    @State private var selectedCategory = "Personal"
    @State private var selectedPriority = 1

    let categories = ["Personal", "Work", "Shopping", "Health"]
    let priorities = [1, 2, 3, 4, 5]

    var body: some View {
        Form {
            Section {
                Picker("Category", selection: $selectedCategory) {
                    ForEach(categories, id: \.self) { category in
                        Text(category).tag(category)
                    }
                }

                Picker("Priority", selection: $selectedPriority) {
                    ForEach(priorities, id: \.self) { priority in
                        Text("Priority \(priority)").tag(priority)
                    }
                }
                .pickerStyle(.segmented)
            }
        }
        .navigationTitle("Task Details")
    }
}

Date Picker

swift
struct DatePickerForm: View {
    @State private var selectedDate = Date()
    @State private var startDate = Date()
    @State private var endDate = Date().addingTimeInterval(86400)

    var body: some View {
        Form {
            Section {
                DatePicker("Date", selection: $selectedDate)

                DatePicker("Start Date", selection: $startDate)
                DatePicker("End Date", selection: $endDate, displayedComponents: .date)
            }

            Section {
                Text("Selected: \(selectedDate.formatted(date: .long, time: .omitted))")
                Text("Range: \(startDate.formatted()) - \(endDate.formatted())")
            }
        }
        .navigationTitle("Date Selection")
    }
}

Toggle

swift
struct ToggleForm: View {
    @State private var notificationsEnabled = true
    @State private var darkModeEnabled = false
    @State private var locationTrackingEnabled = false

    var body: some View {
        Form {
            Section {
                Toggle("Enable Notifications", isOn: $notificationsEnabled)
                Toggle("Dark Mode", isOn: $darkModeEnabled)
                Toggle("Location Tracking", isOn: $locationTrackingEnabled)
            } header: {
                Text("Settings")
            } footer: {
                Text("Manage your app preferences")
            }
        }
        .navigationTitle("Preferences")
    }
}

Advanced Form Features

Stepper

swift
struct StepperForm: View {
    @State private var quantity = 1
    @State private var temperature = 20.0

    var body: some View {
        Form {
            Section {
                Stepper("Quantity: \(quantity)", value: $quantity, in: 1...100)

                Stepper("Temperature: \(Int(temperature))°C", value: $temperature, in: 0...50, step: 0.5)
            }
        }
        .navigationTitle("Values")
    }
}

Slider

swift
struct SliderForm: View {
    @State private var rating = 3.0
    @State private var volume = 0.5

    var body: some View {
        Form {
            Section {
                VStack(alignment: .leading) {
                    Text("Rating: \(rating, specifier: "%.1f")")
                    Slider(value: $rating, in: 1...5, step: 0.5)
                }

                VStack(alignment: .leading) {
                    Text("Volume: \(Int(volume * 100))%")
                    Slider(value: $volume, in: 0...1)
                }
            }
        }
        .navigationTitle("Adjustments")
    }
}

Form Submission

Submit with Loading

swift
struct SubmitForm: View {
    @State private var isLoading = false
    @State private var showAlert = false
    @State private var alertMessage = ""

    @State private var name: String = ""
    @State private var email: String = ""

    var body: some View {
        Form {
            Section {
                TextField("Name", text: $name)
                TextField("Email", text: $email)
            }

            Section {
                if isLoading {
                    HStack {
                        Spacer()
                        ProgressView()
                        Spacer()
                    }
                } else {
                    Button("Submit") {
                        submitForm()
                    }
                    .disabled(name.isEmpty || email.isEmpty)
                }
            }
        }
        .navigationTitle("Submit")
        .alert("Status", isPresented: $showAlert) {
            Button("OK", role: .cancel) { }
        } message: {
            Text(alertMessage)
        }
    }

    private func submitForm() {
        isLoading = true

        Task {
            try? await Task.sleep(nanoseconds: 2 * 1_000_000_000)

            await MainActor.run {
                isLoading = false
                alertMessage = "Form submitted successfully!"
                showAlert = true
            }
        }
    }
}

Focused State

Keyboard Management

swift
struct FocusedForm: View {
    enum Field: Hashable {
        case name
        case email
        case message
    }

    @State private var name = ""
    @State private var email = ""
    @State private var message = ""
    @FocusState private var focusedField: Field?

    var body: some View {
        Form {
            Section {
                TextField("Name", text: $name)
                    .focused($focusedField, equals: .name)

                TextField("Email", text: $email)
                    .focused($focusedField, equals: .email)
                    .keyboardType(.emailAddress)

                TextEditor(text: $message)
                    .focused($focusedField, equals: .message)
            }

            Section {
                Button("Submit") {
                    focusedField = nil
                }
            }
        }
        .toolbar {
            ToolbarItemGroup(placement: .keyboard) {
                Button("Previous") {
                    switch focusedField {
                    case .email:
                        focusedField = .name
                    case .message:
                        focusedField = .email
                    default:
                        focusedField = nil
                    }
                }
                .disabled(focusedField == .name)

                Button("Next") {
                    switch focusedField {
                    case .name:
                        focusedField = .email
                    case .email:
                        focusedField = .message
                    default:
                        focusedField = nil
                    }
                }
                .disabled(focusedField == .message)

                Spacer()

                Button("Done") {
                    focusedField = nil
                }
            }
        }
    }
}

Best Practices

  1. 1Validation: Provide clear validation feedback
  2. 2Layout: Group related fields
  3. 3Feedback: Show loading states
  4. 4Accessibility: Add accessibility labels
  5. 5Keyboard: Use appropriate keyboard types
  6. 6Focus: Manage focus for better UX
  7. 7Error Handling: Handle errors gracefully
  8. 8Testing: Test form thoroughly

SwiftUI forms provide a great user experience when done right!

Related essays

Next essay
Swift Accessibility Guide: Inclusive iOS Development