SwiftUI Patterns

Real-Time Form Validation

June 21, 2026
5 min read
Featured image for blog post: Real-Time Form Validation

Forms with real-time validation provide instant feedback. Here's how to build them with computed properties.

Follow along with the code: iOS-Practice on GitHub

The Form Model

class RegistrationFormModel: ObservableObject {
    @Published var fullName = ""
    @Published var email = ""
    @Published var password = ""
    @Published var confirmPassword = ""
    @Published var country = ""
    @Published var age = 18
    @Published var acceptedTerms = false

    let countries = ["United States", "Canada", "United Kingdom", "Australia"]
}

Computed Validation Properties

var fullNameError: String? {
    guard !fullName.isEmpty else { return nil }
    if fullName.count < 2 {
        return "Name must be at least 2 characters"
    }
    return nil
}

var emailError: String? {
    guard !email.isEmpty else { return nil }
    let emailRegex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/
    if email.wholeMatch(of: emailRegex) == nil {
        return "Please enter a valid email address"
    }
    return nil
}

var passwordError: String? {
    guard !password.isEmpty else { return nil }
    if password.count < 8 {
        return "Password must be at least 8 characters"
    }
    return nil
}

var confirmPasswordError: String? {
    guard !confirmPassword.isEmpty else { return nil }
    if confirmPassword != password {
        return "Passwords do not match"
    }
    return nil
}

Overall Validity

var isValid: Bool {
    !fullName.isEmpty &&
    fullNameError == nil &&
    !email.isEmpty &&
    emailError == nil &&
    !password.isEmpty &&
    passwordError == nil &&
    !confirmPassword.isEmpty &&
    confirmPasswordError == nil &&
    !country.isEmpty &&
    acceptedTerms
}

The Form View

struct FormValidationExerciseView: View {
    @StateObject private var formModel = RegistrationFormModel()
    @State private var showSuccessAlert = false

    var body: some View {
        Form {
            Section("Personal Info") {
                TextField("Full Name", text: $formModel.fullName)
                    .textContentType(.name)

                if let error = formModel.fullNameError {
                    Text(error)
                        .font(.caption)
                        .foregroundColor(.red)
                }

                TextField("Email", text: $formModel.email)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)
                    .autocapitalization(.none)

                if let error = formModel.emailError {
                    Text(error)
                        .font(.caption)
                        .foregroundColor(.red)
                }
            }

            Section("Password") {
                SecureField("Password", text: $formModel.password)
                    .textContentType(.newPassword)

                if let error = formModel.passwordError {
                    Text(error)
                        .font(.caption)
                        .foregroundColor(.red)
                }

                SecureField("Confirm Password", text: $formModel.confirmPassword)

                if let error = formModel.confirmPasswordError {
                    Text(error)
                        .font(.caption)
                        .foregroundColor(.red)
                }
            }

            Section {
                Button("Register") {
                    submitForm()
                }
                .disabled(!formModel.isValid)
            }
        }
    }
}

Why Computed Properties?

  1. Real-time - Updates as user types
  2. No state management - Derived from source
  3. Testable - Pure functions of input
  4. Composable - Combine for overall validity

Key Patterns

Show errors only after input:

guard !email.isEmpty else { return nil }

Disable submit until valid:

.disabled(!formModel.isValid)

Use appropriate keyboard types:

.keyboardType(.emailAddress)
.textContentType(.newPassword)

Interview Tip

This demonstrates reactive validation without external libraries. The pattern scales to complex forms with many fields.

Originally published on pixelper.com

© 2026 Christopher Moore / Dead Pixel Studio

Let's work together

Professional discovery, design, and complete technical coverage for your ideas

Get in touch