Building Forms and Inputs in SwiftUI: TextFields, Pickers, Toggles & More
Learn how to build beautiful and functional forms in SwiftUI using TextFields, Pickers, Toggles, and more. From simple inputs to dynamic…

👋🏽 Introduction: Forms — The Gateway to User Data (and Typos 😅)
Forms are the unsung heroes of most apps. Whether you’re signing up for a dating app, ordering groceries, or setting up two-factor authentication that still texts your old number, you’re filling out a form.
And in SwiftUI, creating forms can be surprisingly simple — until it’s not. 🧩
This guide is your shortcut to mastering TextFields, Pickers, Toggles, and more — all wrapped in SwiftUI’s powerful (but sometimes picky) Form container. We’ll break down how to:
- Capture text input like a pro ✍️
- Let users toggle preferences without toggling your mental state
- Offer clean, elegant pickers (even for enums)
- Validate inputs without writing a thesis on regex
If you’ve ever thought “why isn’t this field updating?” or “where did my picker go?”, you’re in the right place.
Let’s start with understanding the Form view and when to use it.
📦 Understanding Form in SwiftUI
The Form container in SwiftUI is like a magical VStack — but smarter. It automatically handles things like spacing, alignment, and scrolling, especially in settings-style interfaces or data-entry views.
But here’s the catch: it’s great until you need to customize it beyond its will. 😅
🔍 So, what is Form?
Think of Form as a specialized container designed for grouping inputs — it works especially well on iOS, where users are accustomed to Apple-style settings screens.
Form {
TextField("Enter your name", text: $name)
Toggle("Enable Notifications", isOn: $notificationsEnabled)
Picker("Favorite Fruit", selection: $selectedFruit) {
Text("🍎 Apple").tag("Apple")
Text("🍌 Banana").tag("Banana")
Text("🍇 Grape").tag("Grape")
}
}
✅ Automatically applies system styling
✅ Supports grouped sections
✅ Handles keyboard and layout quirks (mostly)
✅ Scrolls when content exceeds the screen
🛑 But when should you not use Form?
If you’re building a heavily customized design, animations, or a layout that feels more like Instagram and less like the iOS Settings app — skip Form and go with VStack, ScrollView, or LazyVStack.
✍️ TextFields in SwiftUI: Capturing User Input (Without the Drama)
TextField is your go-to when you want users to type something — whether it’s a name, an email, or that ambitious New Year’s resolution they’ll forget by February.
🧠 Basic Usage
@State private var name = ""
var body: some View {
Form {
TextField("Enter your name", text: $name)
}
}Here’s what’s happening:
- “Enter your name” is the placeholder text.
- text: $name binds the TextField to a @State variable so changes are reflected immediately.
You type → SwiftUI updates the state → UI reflects the new value. That’s the whole loop. Clean. Reactive. Swifty.
⌨️ Customize Keyboard Types
SwiftUI gives you control over the keyboard shown to the user. Want to nudge them toward the right input? Show the right keyboard!
TextField("Email", text: $email)
.keyboardType(.emailAddress)Other keyboard types include:
.default.numberPad.decimalPad.URL.phonePad
🔒 SecureField for Passwords
Need to collect sensitive info without flashing it on screen? Use SecureField:
SecureField("Password", text: $password)Yes, it’s literally the same as TextField — but the text gets hidden, which is great for passwords, PINs, or secret agent codenames.
🧹 Keyboard Dismissal
Here’s the twist: by default, the keyboard doesn’t dismiss automatically in SwiftUI. You need to handle it manually:
extension View {
func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}And then use .onTapGesture or a button to dismiss it when needed.
🔘 Toggles in SwiftUI: On, Off, and Occasionally Confusing
Toggle is SwiftUI’s elegant way of letting users make a binary choice — like yes/no, true/false, or do I really want notifications at 2AM?
🧠 Basic Usage
@State private var notificationsEnabled = false
var body: some View {
Form {
Toggle("Enable Notifications", isOn: $notificationsEnabled)
}
}As soon as the user taps it:
- The
@Statevariable updates - Any view depending on that value re-renders
- And yes, you can even react to the change with
.onChange
🛠 Customize Toggle Labels
Want to make it fun (or serious)? You’re not limited to text:
Toggle(isOn: $notificationsEnabled) {
Label("Push Notifications", systemImage: "bell.fill")
}🌀 Dynamic UI with Toggles
Toggles are especially great when you want to conditionally show or enable other UI elements:
Toggle("Use Advanced Settings", isOn: $useAdvanced)
if useAdvanced {
TextField("Advanced Config", text: $advancedConfig)
}So yes — your toggle can act like a feature gate!
Next stop: Pickers — dropdowns, wheels, and segmented controls that help your users choose just one favorite fruit 🍌🍎🍇
🎡 Pickers in SwiftUI: Let Users Choose (But Only One 😅)
Pickers are SwiftUI’s answer to dropdown menus, wheel pickers, and segmented controls. They’re perfect when users need to choose a single item from a list — like selecting a favorite fruit, theme, or whether pineapple belongs on pizza (jury’s still out).
🧠 Basic Picker Example
@State private var selectedFruit = "Apple"
let fruits = ["Apple", "Banana", "Grape"]
var body: some View {
Form {
Picker("Select a fruit", selection: $selectedFruit) {
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
}
}
}
}📝 Notes:
- selection binds the
Pickerto a state variable. - Inside, we loop over options using
ForEach. - The Picker will render as a list-style selector on iOS by default (think: push-to-select).
💼 Using Enums with Pickers
Cleaner, safer, and easier to maintain!
enum Fruit: String, CaseIterable, Identifiable {
case apple, banana, grape
var id: String { self.rawValue }
}
@State private var selectedFruit: Fruit = .apple
Picker("Choose wisely", selection: $selectedFruit) {
ForEach(Fruit.allCases) { fruit in
Text(fruit.rawValue.capitalized)
}
}✅ Type-safe
✅ Works with .tag() automatically
✅ Easier to read than stringly-typed logic
🎛 Styling Pickers
You can switch between different styles:
Picker("Theme", selection: $theme) {
Text("Light").tag("light")
Text("Dark").tag("dark")
Text("System").tag("system")
}
.pickerStyle(.segmented)Other styles include:
.wheel.menu.inline.navigationLink(default for Form)
Segmented pickers work best for just 2–4 options. Any more and it becomes a game of Tap & Pray 😅
🔄 Dynamic & Conditional Inputs: Smart Forms FTW
A truly delightful form doesn’t just sit there — it adapts. With SwiftUI, you can easily show, hide, or update inputs based on user actions like toggling a switch or picking an option.
Let’s turn static forms into reactive ones.
🧪 Show Inputs Based on Toggle
Want to reveal advanced settings only when the user enables them? Here’s how:
@State private var useAdvanced = false
@State private var config = ""
Form {
Toggle("Use Advanced Settings", isOn: $useAdvanced)
if useAdvanced {
TextField("Advanced Config", text: $config)
}
}Simple, readable, and super effective. The moment the toggle flips, the TextField pops in.
🧼 Enable/Disable Based on Selection
Maybe you don’t want to hide a field — just disable it until it’s relevant:
@State private var isSubscribed = false
@State private var email = ""
Form {
Toggle("Subscribe to newsletter", isOn: $isSubscribed)
TextField("Email", text: $email)
.disabled(!isSubscribed)
.opacity(isSubscribed ? 1 : 0.5)
}👆 This combo helps users understand what’s interactive right now, while keeping the UI stable.
📦 Nested Pickers or Toggles? Go for it.
Forms often cascade. One input opens the door to another:
@State private var deliveryMethod = "Pickup"
@State private var address = ""
Form {
Picker("Delivery Method", selection: $deliveryMethod) {
Text("Pickup").tag("Pickup")
Text("Home Delivery").tag("Delivery")
}
if deliveryMethod == "Delivery" {
TextField("Enter your address", text: $address)
}
}And there you have it — forms that behave like they actually care about what the user is doing! 💁♂️
Next up: let’s level up the UX of these forms — spacing, sections, keyboard magic, and all the shiny polish that makes a big difference.
✨ UX Tips for Better Forms: Because Design Isn’t Just for Designers
Even the most functional form can feel annoying if it’s not thoughtfully laid out. Spacing, grouping, and flow matter more than we admit (just like proper coffee-to-milk ratio ☕️).
Here’s how to make your SwiftUI forms feel delightful — not like tax forms.
📚 Group Related Inputs
Use Section to group related form fields together. This improves readability and makes forms feel less overwhelming.
Form {
Section(header: Text("Account Info")) {
TextField("Username", text: $username)
SecureField("Password", text: $password)
}
Section(header: Text("Preferences")) {
Toggle("Enable Dark Mode", isOn: $darkMode)
Picker("Theme", selection: $selectedTheme) {
Text("Light").tag("light")
Text("Dark").tag("dark")
Text("System").tag("system")
}
}
}
📌 Tip: You can also add footer: for extra guidance like “We’ll never share your password. Mostly.”
🪄 Manage Keyboard Behavior
Ah yes — the keyboard. The invisible troll that loves covering your inputs.
Here’s what helps:
- Use
.submitLabel()to hint what the return key should do:
TextField("Email", text: $email)
.submitLabel(.next)- Use
.onSubmit {}to handle return key taps:
TextField("Username", text: $username)
.submitLabel(.next)
.onSubmit {
focusField = .password
}🧠 Control Focus Like a Pro with @FocusState (iOS 15+)
Tired of your users manually tapping around the screen to jump from one field to another? @FocusState lets you programmatically control which field has the keyboard focus — making forms feel buttery smooth 🧈
Here’s how it works in simple terms:
@FocusState private var focusField: Field?
enum Field {
case username
case password
}Then you assign each field:
TextField("Username", text: $username)
.focused($focusField, equals: .username)
SecureField("Password", text: $password)
.focused($focusField, equals: .password)And now you can shift focus dynamically:
.onSubmit {
focusField = .password // Moves to the password field
}✨ Poof! The keyboard jumps to the next field — no magic wands or UIKit needed.
🧘♂️ Leave Breathing Room
Even with Form, it helps to:
- Add padding(
.vertical) or padding(.top) on Sections - Avoid crowding labels and fields
- Give toggles some vertical space to breathe
Your UI will look more Apple-y and less like a cramped Excel sheet.
🛡️ Form Validation Basics: Save Your Future Self from Garbage Data
Forms are fun… until users start submitting “123@example” as their email or try to log in with an empty password. That’s where validation comes in.
SwiftUI doesn’t give us a built-in validation engine, but it does give us the tools to build lightweight, real-time validation.
Let’s explore the essentials.
✅ Inline Validation with State
@State private var email = ""
@State private var showValidation = false
var isEmailValid: Bool {
email.contains("@") && email.contains(".")
}Use the validation logic to provide visual feedback:
TextField("Email", text: $email)
.autocapitalization(.none)
.onChange(of: email) { _ in
showValidation = true
}
if showValidation && !isEmailValid {
Text("Please enter a valid email")
.foregroundColor(.red)
.font(.caption)
}🔁 This is reactive: the moment users type something invalid, they get feedback — gently nudging them without a popup yelling at their face.
🙅♂️ Disabling Submit Until Ready
Want to prevent “Submit” from being tapped before the form is valid? Just combine validation with the button state:
Button("Submit") {
submitForm()
}
.disabled(!isEmailValid)You can also make the button look visibly inactive:
.opacity(isEmailValid ? 1 : 0.5)🛠 Optional: Manual Validation on Submit
Sometimes you don’t want to validate on-the-fly, but only when the user hits the big red button:
Button("Submit") {
if !isEmailValid {
errorMessage = "Invalid email!"
} else {
submitForm()
}
}Up to you — proactive inline validation or lazy submission check. Both are valid.
🚀 Submitting the Form: Wrapping It All Up (Nicely)
You’ve got the inputs, the validation, the reactive flow — now it’s time to collect the data and do something with it. Whether it’s saving locally, hitting an API, or just logging the values for now, the submit action ties it all together.
🧼 Basic Submit Button
At its core, submitting a form is just a button with a handler:
Button("Submit") {
submitForm()
}Add a touch of UX polish by disabling it when validation fails:
Button("Submit") {
submitForm()
}
.disabled(!formIsValid)
.opacity(formIsValid ? 1 : 0.5)💡 This prevents accidental taps and signals clearly that something’s incomplete.
🧾 Showing Alerts on Submit
Want to confirm the action or give user feedback? SwiftUI makes it easy with .alert.
@State private var showAlert = false
Button("Submit") {
if formIsValid {
showAlert = true
}
}
.alert("Form Submitted!", isPresented: $showAlert) {
Button("OK", role: .cancel) { }
}Great for:
- Simple confirmations
- Friendly nudges like “Thanks for signing up! 🥳”
- Or gently shaming incomplete forms (…nicely!)
🏃♂️ Combine It with Navigation or API Calls
Here’s where you’d typically:
- Send data to your
ViewModel - Trigger an API call
- Navigate to the next screen on success
Button("Submit") {
Task {
await viewModel.submitFormData()
}
}you’ve got yourself a real user flow.
Next: Before we wrap up the whole article, let’s quickly look at some common pitfalls developers hit when building SwiftUI forms — so you can dodge them like a pro 🕴
🐞 Common Pitfalls in SwiftUI Forms (and How to Dodge Them)
SwiftUI makes form-building feel like magic… until it doesn’t. If your TextField stops updating, your Picker vanishes into the void, or your Form starts randomly scrolling like it has a mind of its own — you’re not alone.
Here are some of the most common mistakes (and how not to make them 👇):
1️⃣ ❌ Using @State for Shared Data When You Should Use @StateObject
If you’re sharing form data across views, don’t keep it trapped in local @State. Use an observable model:
class FormModel: ObservableObject {
@Published var name = ""
@Published var email = ""
}
@StateObject var form = FormModel() // or @ObservedObject in child viewsThis helps with:
- Validation across fields
- Keeping state in sync across multiple views
Detailed explanation on, How to figure out which State you need to wrap your property with?
**Mastering SwiftUI State Management: @State vs @Binding vs @ObservedObject vs @StateObject (2025…** _Confused between @State, @Binding, @ObservedObject, and @StateObject in SwiftUI? You’re not alone. This 2025 guide…_medium.comhttps://medium.com/swift-pal/mastering-swiftui-state-management-state-vs-binding-vs-observedobject-vs-stateobject-2025-e1f751aa038d
2️⃣ ❌ Forgetting to Bind Selection Properly in Pickers
If your Picker doesn’t reflect the selected value, check:
- You’ve tagged each Text with
.tag() - Your selection value matches the data type
Picker("Choose", selection: $choice) {
Text("A").tag("A") // ✅ Always tag
}3️⃣ ❌ Not Managing Focus — Result: Keyboard Chaos
Leaving the keyboard to block fields or never dismiss is a SwiftUI rite of passage. Beat it early with @FocusState, as we discussed earlier.
Bonus tip: wrap your Form in a tap gesture to dismiss the keyboard:
.onTapGesture {
hideKeyboard()
}4️⃣ ❌ Not Providing Visual Cues for Disabled Inputs
Just disabling a field without changing its appearance? That’s confusing.
TextField("Email", text: $email)
.disabled(true)
.opacity(0.5) // 👈 Nice touch!5️⃣ ❌ Overusing .onChange Without Throttling
It’s tempting to slap .onChange(of:) on every field, but if you’re doing expensive work (like API calls or validations), consider throttling or debouncing it.
For more reactive needs, Combine or debounced async logic is your friend.
🧘♀️ TL;DR: Make Forms Work With You, Not Against You
- Use
Formsmartly — not blindly - Control input state clearly
- Don’t fight the keyboard — tame it
- Validate gently, not with passive-aggressive alerts
- Keep user experience smooth, not mysterious
✅ Wrap-Up: You’re Now Form-Ready 🧾💪
And there you have it — a complete guide to building forms and inputs in SwiftUI that actually behave, validate, and look good doing it.
You’ve learned how to:
- Use
Form,TextField,Picker, andTogglelike a SwiftUI ninja - Add dynamic logic to show/hide fields like a mind reader 🔮
- Validate user input so your backend doesn’t scream
- Polish the user experience with focus management, button states, and layout tips
Forms are the front door to user data. Now that yours are shiny, accessible, and well-behaved — you’re way ahead of the pack.
Next in the SwiftUI Mastery Series
**🎨 Styling in SwiftUI: Build Beautiful Apps with Custom Themes**
Learn how to use custom fonts, colors, theming, dark mode, and modifiers to design apps that don’t look like they were built in 2010. It’s not just code — it’s ✨aesthetic✨.
**SwiftUI Styling Guide: Fonts, Themes, Dark Mode & Why Order Matters** _🎨 Give your SwiftUI app a complete style upgrade! Learn how to add custom fonts, build adaptive themes, support dark…_medium.comhttps://medium.com/swift-pal/swiftui-styling-guide-fonts-themes-dark-mode-why-order-matters-7fbf5389d384
🧭 Where to Go From Here?
If you liked this article, you’ll love the rest of the SwiftUI Mastery Series — and beyond. From async networking to advanced architecture, here’s your full roadmap to becoming an iOS legend:
👉 **Explore the Ultimate iOS Article Index**
Inside, you’ll find everything I’ve written — organized, beginner-to-expert, and updated for 2025 and iOS 26.
🎉 Enjoyed this article? Your support means the world to me!
💬 Drop a comment below! I love hearing about your experiences and answering questions
🎬 Subscribe on Youtube and become early subscribers of my channel: https://www.youtube.com/@swift-pal
💼 Let’s connect on LinkedIn for more professional insights: https://www.linkedin.com/in/karan-pal
Happy coding! 🚀
New articles, straight to your inbox.
No spam, no filler — just new writing on iOS, the web, and AI when it ships. Unsubscribe anytime.
Keep reading
Why I'm Rebuilding My Blog From Scratch (and Leaving Medium)
After years of publishing on someone else's platform, I'm moving my writing to a home I actually own. Here's the reasoning, and what I'm building instead.
ReadData Is the Model: The Most Ignored Part of AI
A beginner-friendly guide to why data quality beats model hype.
Read