How to use @FocusState in SwiftUI | SwiftUI Bootcamp #60
Managing keyboard focus used to mean juggling UIKit's becomeFirstResponder calls — @FocusState gives you a clean, declarative way to control which text field is active, so you can build smart onboarding flows and multi-field forms that guide users through inputs automatically.
What You'll Learn
- How to declare
@FocusStatewith an enum to track multiple fields - How to bind focus to a specific field using the
focused(_:equals:)modifier - How to programmatically move focus from one field to another in response to user actions
Mental Model
Think of @FocusState as a spotlight in a theater. At any moment, only one performer (text field) can stand in the spotlight (have keyboard focus). The spotlight operator (@FocusState variable) decides who is lit up. When you tap a field, the spotlight moves there automatically. But you — the developer — can also grab the spotlight control and move it programmatically: "the username field is empty, so move the spotlight there right now."
The optional enum approach (OnboardingField?) means the spotlight can also be fully off — when fieldInFocus is nil, no field has focus and the keyboard is dismissed.
Detailed Explanation
@FocusState is a property wrapper introduced in iOS 15 that lets you read and write which view currently holds keyboard focus. Unlike plain @State, it creates a two-way binding between SwiftUI's focus system and your variable — SwiftUI updates it when the user taps a field, and your code can update it to move focus elsewhere.
Under the hood, SwiftUI monitors the focus engine and reconciles your @FocusState value with the actual first responder. When you write fieldInFocus = .password, SwiftUI schedules a focus change on the next render pass. This is why you should always set focus values on the main thread and within user-driven actions or lifecycle events — setting them mid-layout can be unpredictable.
Use @FocusState when you need: automatic field progression after the user finishes typing, showing or hiding UI based on whether a field is active (like a toolbar), or dismissing the keyboard programmatically by setting the value to nil. Avoid it when all you need is a simple "auto-focus on appear" — in that case, .onAppear with a short DispatchQueue.main.asyncAfter is simpler.
@FocusState pairs naturally with @State (for the text content) and onSubmit (for reacting to the keyboard's return key). The enum pattern shown here scales well — just add a new case for each field you want to track.
Code Structure
FocusStateBootcamp.swift contains a single screen with two text fields and a sign-up button. The OnboardingField enum gives each field a typed identity. Commented-out lines show the earlier Boolean approach (@FocusState private var usernameInFocus: Bool) so you can see why the enum pattern is preferable for managing multiple fields.
Complete Code
FocusStateBootcamp.swift
import SwiftUI
struct FocusStateBootcamp: View {
// Enum gives each field a unique, type-safe identity for focus tracking
enum OnboardingField: Hashable {
case username
case password
}
// @FocusState private var usernameInFocus: Bool
@State private var username: String = ""
// @FocusState private var passwordInFocus: Bool
@State private var password: String = ""
// Single @FocusState with an enum replaces one Bool per field
@FocusState private var fieldInFocus: OnboardingField?
var body: some View {
VStack(spacing: 30) {
TextField("Add your name here...", text: $username)
.focused($fieldInFocus, equals: .username) // highlights when fieldInFocus == .username
// .focused($usernameInFocus)
.padding(.leading)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.gray.brightness(0.3))
.cornerRadius(10)
SecureField("Add your password here...", text: $password)
.focused($fieldInFocus, equals: .password) // highlights when fieldInFocus == .password
// .focused($passwordInFocus)
.padding(.leading)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.gray.brightness(0.3))
.cornerRadius(10)
Button("SIGN UP 🚀") {
let usernameIsValid = !username.isEmpty
let passwordIsValid = !password.isEmpty
if usernameIsValid && passwordIsValid {
print("SIGN UP") // Both fields valid — proceed with sign-up
} else if usernameIsValid {
fieldInFocus = .password // Username done, jump focus to password
// usernameInFocus = false
// passwordInFocus = true
} else {
fieldInFocus = .username // Username is empty — bring focus back
// usernameInFocus = true
// passwordInFocus = false
}
}
// Button("TOGGLE FOCUS STATE") {
// usernameInFocus.toggle()
// }
}
.padding(40)
// .onAppear {
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
// self.usernameInFocus = true
// }
// }
}
}
struct FocusStateBootcamp_Previews: PreviewProvider {
static var previews: some View {
FocusStateBootcamp()
}
}Code Walkthrough
OnboardingFieldenum — Defining an enum that conforms toHashableis the recommended pattern for tracking multiple fields. Each case represents one focusable field. The enum approach is better than separate Booleans because it makes only one field active at a time by design.@FocusState private var fieldInFocus: OnboardingField?— The optional type is key. WhenfieldInFocusisnil, no field has focus and the keyboard is dismissed. SwiftUI automatically sets this tonilwhen the user taps outside a field..focused($fieldInFocus, equals: .username)— This modifier registers the text field with the focus system. It creates a binding: the field gains focus whenfieldInFocus == .username, and it updatesfieldInFocusto.usernamewhen the user taps on it.Validation logic in the button — Rather than always submitting, the button checks each field and moves focus to the first empty one. This is a pattern common in onboarding flows — it keeps the keyboard on screen and guides the user to what needs filling in.
Commented
DispatchQueue.main.asyncAfter— This is the older workaround for auto-focusing a field on appear. With@FocusStateyou can still do this, but it requires the small delay because the view must be fully rendered before focus can be set.
Common Mistakes
Mistake: Using @FocusState without @State for the text value@FocusState only tracks focus — it never holds the text. You always need a separate @State var for the actual string content. Confusing the two leads to text not persisting when you switch fields.
Mistake: Setting fieldInFocus synchronously in onAppear
SwiftUI lays out views before focus can be requested. Setting @FocusState directly in onAppear often has no effect. Wrap it in DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) to let the view settle first.
Mistake: Using a plain Bool for more than one field
When you have two separate @FocusState Booleans, nothing prevents both from being true simultaneously. The enum pattern guarantees mutual exclusivity — only one case can be active at a time.
Key Takeaways
@FocusStatewith an enum is the modern, scalable way to manage focus across multiple text fields- Setting the variable to
nildismisses the keyboard — no need for.resignFirstResponderworkarounds - Focus changes should happen in response to user actions or lifecycle events, not mid-layout
Last updated: June 27, 2026