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
- 1Validation: Provide clear validation feedback
- 2Layout: Group related fields
- 3Feedback: Show loading states
- 4Accessibility: Add accessibility labels
- 5Keyboard: Use appropriate keyboard types
- 6Focus: Manage focus for better UX
- 7Error Handling: Handle errors gracefully
- 8Testing: Test form thoroughly
SwiftUI forms provide a great user experience when done right!