Forms & User Input
Reviewed & published by Brayan K
Apps live on user input. SwiftUI's Form gives you a polished, settings-style container, and controls like TextField, Toggle, Picker, and Stepper read and write your state through bindings. Wire them up and your screen becomes interactive.
Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
Building iOS Apps with SwiftUI • Intermediate
What You'll Learn in This Lesson
- • Lay out input with Form and Section
- • Capture text with TextField / SecureField
- • Flip booleans with Toggle
- • Choose options with Picker
- • Adjust numbers with Stepper via $ bindings
1️⃣ Form & Section
Form arranges controls into a scrollable, settings-style layout. Group related controls with Section, optionally adding a header. SwiftUI handles all the styling.
import SwiftUI
// Form groups input controls into a styled, scrollable layout — perfect for
// settings and sign-up screens. Use Section to group related controls.
struct SettingsForm: View {
@State private var username = ""
@State private var notifications = true
var body: some View {
Form {
Section("Account") {
TextField("Username", text: $username)
}
Section("Preferences") {
Toggle("Notifications", isOn: $notifications)
}
}
}
}2️⃣ TextField — Capturing Text
TextField binds to a String via the $ prefix, so typing updates your state live. Modifiers like .keyboardType tune the keyboard, and SecureField hides sensitive input.
import SwiftUI
// TextField binds to a String. The $ prefix passes a two-way Binding so what
// the user types flows into your @State, and vice versa.
struct LoginFields: View {
@State private var email = ""
@State private var password = ""
var body: some View {
Form {
TextField("Email", text: $email)
.keyboardType(.emailAddress) // email-friendly keyboard
.textInputAutocapitalization(.never)
SecureField("Password", text: $password) // hides characters
// Live preview of the bound state:
Text("Typing: \(email)")
}
}
}3️⃣ Toggle, Picker & Stepper
Toggle binds to a Bool, Picker to a selection (matched by each option's tag), and Stepper to a number it can increment within a range. All read and write through bindings.
import SwiftUI
// Toggle binds to a Bool, Picker to a selection, Stepper to a number.
struct OrderForm: View {
@State private var isGift = false
@State private var size = "Medium"
@State private var quantity = 1
let sizes = ["Small", "Medium", "Large"]
var body: some View {
Form {
Toggle("Wrap as gift", isOn: $isGift)
Picker("Size", selection: $size) {
ForEach(sizes, id: \.self) { s in
Text(s).tag(s) // tag matches the selection value
}
}
Stepper("Quantity: \(quantity)", value: $quantity, in: 1...10)
}
}
}Your turn. Fill in the blanks to wire up a small sign-up form.
// 🎯 YOUR TURN — fill in each ___ then preview in Xcode.
import SwiftUI
struct SignUpForm: View {
@State private var name = ""
@State private var agreed = false
@State private var age = 18
var body: some View {
Form {
// 1) A text field bound to 'name' (pass it as a binding)
TextField("Your name", text: ___name) // 👉 the binding prefix
// 2) A toggle bound to 'agreed'
___("I agree to the terms", isOn: $agreed) // 👉 the on/off control
// 3) A stepper that changes 'age' between 13 and 120
Stepper("Age: \(age)", value: $age, in: 13...120)
}
}
}Pro Tips
- 💡 Always pass the $ binding. Controls need read-write access, not a copy of the value.
- 💡 Tune the keyboard. Use .keyboardType(.emailAddress) or .numberPad to help users.
- 💡 Tag your Picker options so the selection value matches each row.
Common Errors (and the fix)
- "Cannot convert value of type 'String' to expected 'Binding<String>'" — you forgot the $. Write TextField("Email", text: $email).
- Picker selection never changes — your options' .tag(...) values don't match the type of the selection binding.
- Stepper goes out of bounds — add a range: value: $quantity, in: 1...10.
Mini-Challenge: Booking Form
Build a Form with two sections. In "Guest" put a TextField for the name. In "Details" put a Picker to choose a room type from three options, a Stepper for the number of nights (1 to 14), and a Toggle for "Include breakfast". Show a live summary Text below that reflects all the bound state.
🎉 Lesson Complete!
- ✅ Form + Section give a styled, scrollable input layout
- ✅ TextField/SecureField bind to a String
- ✅ Toggle → Bool, Picker → selection, Stepper → number
- ✅ Every control reads and writes through a $ binding
Practice quiz
What is the Form view used for?
- Drawing charts
- Grouping controls for data entry in a styled, scrollable container
- Networking
- Playing audio
Answer: Grouping controls for data entry in a styled, scrollable container. Form groups input controls into a platform-styled, scrollable settings-like layout.
What does TextField bind to?
- A Bool
- A String (the editable text) via a binding
- An Int only
- Nothing
Answer: A String (the editable text) via a binding. TextField takes a Binding to a String so typing updates your state and vice versa.
Which prefix passes a binding to a control like TextField?
- #
- &
- $ (the projected value)
- @
Answer: $ (the projected value). The $ prefix turns an @State into the Binding the control reads and writes.
What does Toggle bind to?
- A String
- A Double
- An array
- A Bool (on/off state) via a binding
Answer: A Bool (on/off state) via a binding. Toggle reads and writes a Bool through its isOn binding.
What is Picker used for?
- Picking colors only
- Choosing one option from several, bound to a selection
- Cropping images
- Sorting lists
Answer: Choosing one option from several, bound to a selection. Picker lets the user choose one value from a set, bound to a selection property.
What does the selection binding of a Picker track?
- The chosen option's tag/value
- The screen brightness
- The list length
- Nothing
Answer: The chosen option's tag/value. The selection binding holds the currently chosen option, matched to each option's tag.
What does Stepper bind to and do?
- A Bool it toggles
- A numeric value it increments and decrements
- A String it edits
- A date only
Answer: A numeric value it increments and decrements. Stepper increases or decreases a bound numeric value with + and − buttons.
Why does a TextField need a binding rather than a plain value?
- For color
- So edits flow back into your state (two-way)
- To make it faster
- It does not need one
Answer: So edits flow back into your state (two-way). A binding is two-way: the field displays the state and writes the user's edits back to it.
What does the .keyboardType(.emailAddress) modifier do?
- Validates the email
- Shows a keyboard suited to typing an email
- Sends the email
- Encrypts input
Answer: Shows a keyboard suited to typing an email. .keyboardType configures which on-screen keyboard appears for a TextField.
How do you group related controls within a Form?
- Section { ... }
- Group all in a ZStack
- Use Spacer
- You cannot group
Answer: Section { ... }. Wrap related controls in a Section to give a Form visually separated groups with optional headers.
Continue this course
- Previous: Lists, ForEach & Navigation
- Next: Animations & Transitions — Implicit vs explicit animation, withAnimation and view transitions
Frequently asked questions
What is a Form in SwiftUI?
Form is a container that arranges input controls into a platform-styled, scrollable layout — the look you see in iOS Settings. It automatically styles rows, groups, and section headers, so you focus on the controls rather than the chrome. Inside a Form you place TextField, Toggle, Picker, Stepper, and Sections to group them.
How does TextField update my state?
TextField takes a two-way Binding to a String, which you pass with the $ prefix on an @State property: TextField("Email", text: $email). As the user types, the binding writes into your @State, and if you change the state in code, the field updates to match. That two-way flow keeps the UI and your data in sync.
What do Toggle, Picker, and Stepper bind to?
Toggle binds to a Bool (on/off) via its isOn binding. Picker binds a selection to whichever option's tag matches — choosing an option writes that value into your state. Stepper binds to a numeric value and increments or decrements it with its + and − buttons, optionally constrained to a range.
Why do controls need bindings instead of plain values?
Input controls are two-way: they must display your current state AND write the user's changes back into it. A plain value is a one-way snapshot — the control couldn't update your data. A Binding (passed with $) gives the control read-and-write access to a single source of truth, so edits stay in sync.
How do I group controls and add headers in a Form?
Wrap related controls in a Section, optionally with a header string: Section("Account") { ... }. Sections give a Form visually separated groups with titles and spacing, exactly like grouped settings screens. You can have as many Sections as you need within one Form.