The focus APIs make views selectable through the tvOS remote, hardware keyboards, and the
watchOS crown, and let you style the focused state. focusable and isFocused work on all
Apple platforms; the default-focus APIs shown below are watchOS and tvOS.
Making Views Focusable
focusable(_:interactions:) opts a custom view into the focus system. List and Button
are focusable already; do not add the modifier to them. The FocusInteractions argument
declares what focus is for on that view: activate, edit, or automatic.
ScrollView(.horizontal) {
LazyHStack {
ForEach(0..<100) { index in
PosterView()
.focusable(true, interactions: .automatic)
}
}
}
Reacting to Focus
The isFocused environment value reports whether the nearest focusable ancestor has focus,
so a child can style itself without being focusable.
struct PosterView: View {
@Environment(\.isFocused) var isFocused
var body: some View {
RoundedRectangle(cornerRadius: 8)
.frame(width: 100, height: 150)
.scaleEffect(isFocused ? 1.2 : 1)
.animation(.easeInOut, value: isFocused)
}
}
focusEffectDisabled() removes the system focus ring so you can draw your own indicator
from isFocused.
Default Focus (watchOS and tvOS)
prefersDefaultFocus(_:in:) names the view focus should land on first, scoped to a
@Namespace that the ancestor declares with focusScope. The Bool lets the preference
follow state, so an empty form focuses the field and a filled one focuses the button.
@Namespace private var namespace
@Environment(\.resetFocus) var resetFocus
VStack {
TextField("email", text: $email)
.prefersDefaultFocus(!hasFilledCredentials, in: namespace)
SecureField("password", text: $password)
Button("login") {}
.prefersDefaultFocus(hasFilledCredentials, in: namespace)
Button("reset credentials") {
hasFilledCredentials = false
resetFocus(in: namespace)
}
}
.focusScope(namespace)
The resetFocus environment action sends focus back to the preferred default of a scope.
Multiple namespaces can carve one hierarchy into separate focus scopes with their own entry
points.