Focus Management in SwiftUI: Getting Started
Learn how to manage focus in SwiftUI by improving the user experience for a checkout form. By Mina H. Gerges.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Focus Management in SwiftUI: Getting Started
25 mins
- Getting Started
- Applying Auto-Focus
- Improving Focus Implementation for Multiple Views
- Switching Focus Between Views
- Avoiding Ambiguous Focus Bindings
- Managing Focus in Lists
- Improving Focus Implementation With MVVM
- Observing Values From Focused Views
- Modifying Values From Focused Views
- Where to Go From Here?
Remember the last time you logged in, completed a checkout process, or sent feedback? Each of these interactions likely included a form. Navigating a form can be tedious if the app doesn’t assist with focus. When a view is focused, it’s visually activated and ready for interaction. A view type you might associate with focus is a text field: Often, focus is applied to text fields to bring up the keyboard and tip off the user to type in that field next.
To simplify focus implementation, Apple introduced FocusState
at WWDC 2021. FocusState
is a property wrapper that tracks and edits the focus location in the scene.
In this tutorial, you’ll learn all about focus management in SwiftUI by using modifiers and wrappers like FocusState
to help users navigate forms more effectively. You’ll do so by filling out a checkout form and gift card for a friend. How nice of you! :]
While finding the perfect gift, you’ll learn how to:
- Switch focus between views.
- Handle focus in a list while using the MVVM pattern.
- Recognize and edit a focused view from another view.
Getting Started
Download the starter project by clicking Download Materials at the top or bottom of this tutorial. Open the project in the starter directory in Xcode. Build and run on an iPhone simulator.
The app displays a list of available gifts. Select a gift, then enter shipping information. Finally, write a gift message along with the recipient’s email addresses.
You may notice that some focus improvements could be made. For example, focus should shift seamlessly between shipping fields when the user taps the return key. And, when trying to proceed to the next step, focus should draw the user to invalid entries.
In the next section, you’ll learn about FocusState
and how it can help the user start filling out your form quickly.
If you’re looking for model-view-viewmodel (MVVM) pattern specifically, checkout Design Patterns by Tutorials: MVVM.
If you’re looking for model-view-viewmodel (MVVM) pattern specifically, checkout Design Patterns by Tutorials: MVVM.
Applying Auto-Focus
You’ll start improving the app by implementing auto-focus. Auto-focus is the effect where the first relevant view automatically receives the focus upon loading the screen. Though subtle, it’s an experience users expect.
You’ll use the FocusState
property wrapper to achieve this effect. Generally, FocusState
covers many things:
- Keeping track of which view is currently focused.
- Changing focus to a desired view.
- Removing focus from all views, resulting in keyboard dismissal.
Two modifiers complement FocusState
:
- focused(_:): A modifier that binds the view’s focus to a single Boolean state value.
- focused(_:equals:): A modifier that binds the view’s focus with any given state value.
FocusState
and focused(_:)
are perfect to fix the first UX bug in your app.
Open CheckoutFormView.swift. Below the struct
declaration, add the following line:
@FocusState private var nameInFocus: Bool
This code creates a property to control focus for the name field.
Inside recipientDataView
, add the following modifier to the first EntryTextField
:
.focused($nameInFocus)
This code binds focus for the name field to the nameInFocus
property. nameInFocus
‘s value changes to true
each time the user sets focus on this field.
Add the following under the code you just added:
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) {
self.nameInFocus = true
}
}
This code programmatically applies focus to the Name field when the checkout view appears on screen. The 0.75
second delay is to ensure the view has already appeared.
Build and run. Select a gift, then tap the Checkout button. Once checkout appears, notice how focus shifts to the Name field.
Auto-focus achieved! Doesn’t that make filling out the Checkout Form a bit easier?
In a form with multiple fields, you’ll invariably want to support focus for most fields to assist the user in navigating quickly by tapping return on the keyboard.
With your current implementation, you’d have to add another Bool
property for every field requiring focus. In the next section, you’ll use a better technique to avoid cluttering your view.
Improving Focus Implementation for Multiple Views
Open CheckoutFormView.swift. At the top, add the following code, right before CheckoutFormView
:
enum CheckoutFocusable: Hashable {
case name
case address
case phone
}
This code creates an enum listing all the focusable fields.
Next, inside CheckoutFormView
, replace:
@FocusState private var nameInFocus: Bool
With:
@FocusState private var checkoutInFocus: CheckoutFocusable?
This code creates a property with the type of the enum you just created. This single property holds which field is in focus instead of requiring three different Boolean properties.
Inside recipientDataView
, after the first EntryTextField
, replace the following code:
// 1
.focused($nameInFocus)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) {
// 2
self.nameInFocus = true
}
}
With:
// 1
.focused($checkoutInFocus, equals: .name)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) {
// 2
self.checkoutInFocus = .name
}
}
Here’s what this code does:
- It uses the
focused(_:equals:)
modifier to bind the Name field’s focus to thecheckoutInFocus
property for the enum casename
. - When this view appears on screen, it shifts focus to the Name field by setting the value of
checkoutInFocus
to the corresponding enum case.
The CheckoutFocusable
enum was declared as Hashable
because that’s a requirement for the type used with focused(_:equals:)
.
Build and run. You’ll find the app does exactly as before: When the Checkout screen loads, the Name field is auto-focused.
Now, for those other fields. Inside recipientDataView
, add the following modifier to the second EntryTextField
:
.focused($checkoutInFocus, equals: .address)
This code binds the Address field’s focus to the checkoutInFocus
property for the enum case address
.
You might’ve guessed what you’ll do with the Phone field! Inside recipientDataView
, add the following modifier to the final EntryTextField
:
.focused($checkoutInFocus, equals: .phone)
This code binds the Phone field’s focus to the checkoutInFocus
property for the enum case phone
.
At this point, you’ve set up all the form fields on the checkout screen to handle focus. Next, you’ll use this setup to switch focus between the fields and improve the validation experience.