Forms and input
SwiftUI forms
Learn how forms work in SwiftUI by wrapping controls like TextField, Toggle, and Picker in a Form view that styles them right for each platform.
8 minute lesson
SwiftUI provides several form controls that we can use to get input from the user.
Think about the Settings app on your iPhone. That entire app is a series of forms, built with controls like:
TextFieldTogglePicker- and others
All of those are wrapped in a Form view:
Form {
}
What Form gives you over a VStack
You could put the same controls in a VStack. They would render, but you would have to style everything yourself.
Form tells SwiftUI “this is a form”, and SwiftUI takes over the presentation. On iOS you get the grouped, inset look of the Settings app: rows with separators, the right background, correct paddings and fonts. The controls adapt too. A Picker inside a Form becomes a row showing the current value, and tapping it navigates to the options.
This is the declarative side of SwiftUI at its best. You declare intent, the platform decides presentation.
Sections
Forms grow. Section groups related rows, with an optional header and footer:
Form {
Section("Profile") {
TextField("Username", text: $username)
}
Section {
Toggle("Allow notifications", isOn: $notificationsEnabled)
} footer: {
Text("You can change this later in Settings.")
}
}
The header appears above the group in small capitals. The footer appears below in smaller gray text, perfect for those short explanations you see under rows in the Settings app.
A realistic settings form
Here’s a small settings screen combining the controls we’ll cover in the next lessons:
struct SettingsView: View {
@State private var username = ""
@State private var notificationsEnabled = true
@State private var preview = "Short"
let previews = ["Off", "Short", "Full"]
var body: some View {
Form {
Section("Account") {
TextField("Username", text: $username)
}
Section("Notifications") {
Toggle("Allow notifications", isOn: $notificationsEnabled)
Picker("Preview", selection: $preview) {
ForEach(previews, id: \.self) {
Text($0)
}
}
}
}
}
}
Forms almost always live inside navigation, so wrap the view in a NavigationStack and give it a title:
NavigationStack {
SettingsView()
.navigationTitle("Settings")
}
With about thirty lines you get a screen that looks like Apple built it. No manual styling anywhere.
The same form on other platforms
Run this exact code on a Mac and it renders as a native macOS form: labels aligned in one column, controls in another. You can also opt into the grouped, settings-style look with the .formStyle(.grouped) modifier.
On watchOS the rows become the carousel-style list the platform expects. You wrote the form once, and each platform presents it its own way.
We’ll see more about forms by covering each individual form control.
Lesson completed