WWDC 21
Direct and reflect focus in SwiftUI
SwiftUI in most cases manages focus automatically on your behalf based on given platform's conventions
In more complex cases, where SwiftUI can't figure this out by itself, there are APIs that let you customize the focus behavior
Example situations:
- focusing the text editor of a new note when the "New" button is pressed in Notes
- moving focus from a vertical list of buttons in the sidebar to a horizontal list of items in the main area on tvOS
- programmatically dismissing the keyboard on iOS
The @FocusState API
A view property marked @FocusState
is a special type of state that changes depending on which of the view's subviews is currently focused:
enum Field: Hashable { case email case password } struct ContentView: View { @FocusState private var focusedField: Field? var body: some View { VStack { TextField("Email", text: $email) .focused($focusedField, equals: .email) SecureField("Password", text: $password) .focused($focusedField, equals: .password) } } }
In this case, the Field
type is a custom enum type, but you can also use strings, integers or any other Hashable
type
Notice that the property is an optional, since it's set to nil
if none of the fields is focused – in general, a @FocusState
should always be optional
The binding is two-way – the value changes when the focus changes, but you can also move the focus by modifying the value
For example, you can move the focus back to the email field if the user tries to submit the form, but the email is invalid:
VStack { ... } .onSubmit { if !isEmailValid { focusedField = .email } }
You can also dismiss the keyboard when the form is submitted by setting the value to nil
:
VStack { ... } .onSubmit { if !isEmailValid { focusedField = .email } else { focusedField = nil logIn() } }
Creating navigation targets (tvOS)
On tvOS, all navigation is done by moving focus and then selecting the focused element
You may sometimes want to be able to move focus between two parts of a screen with different content, and the default focus navigation will not work automatically if the two elements in two sections are not directly adjacent to each other
You can fix this by extending the effective focusable area of some elements like buttons to a larger area that is not normally focusable by itself
This is done using the new .focusSection
API:
HStack { VStack { TextField("Email", text: $email) SecureField("Password", text: $password) SignInWithAppleButton(...) } .onSubmit { ... } .focusSection() VStack { Image(photoName) BrowsePhotosButton() } .focusSection() }
When the .focusSection()
view modifier is applied to a view like the VStack
here, the view becomes capable of accepting focus as long as it contains any focusable subviews (the browse button)
Now, the user is able to move from the "Browse photos" button to the login sidebar on the left, even though the buttons in the sidebar aren't directly to the left of the button, and to move right from the email/password fields to the browse button, even though the button isn't directly to the right