$npx -y skills add dpearson2699/swift-ios-skills --skill focus-engineImplements keyboard, directional, and scene-level focus behavior across SwiftUI and UIKit. Use when managing @FocusState, defaultFocus, focused values, focusable interactions, focus sections, tvOS geometric focus and Siri Remote navigation, watchOS Digital Crown input, visionOS c
| 1 | # Focus Engine |
| 2 | |
| 3 | Focus behavior for SwiftUI and UIKit apps targeting iOS 26+, iPadOS, macOS, tvOS, and visionOS connected-input paths. Covers keyboard focus, directional focus, scene-focused values, focus restoration, and UIKit focus guides. `focusSection()` guidance in this skill applies to macOS and tvOS. visionOS gaze-driven hover is an input affordance, not focus. Accessibility-specific focus for VoiceOver and Switch Control lives in the `ios-accessibility` skill. |
| 4 | |
| 5 | When a request mixes focus with accessibility or spatial input, keep the boundary explicit: |
| 6 | - Use this skill for keyboard, remote, game-controller, and scene focus behavior. |
| 7 | - For visionOS, describe gaze, direct touch, and pointer targeting as hover/input affordances, not focus. |
| 8 | - For VoiceOver, Switch Control, Voice Control, or accessibility element ordering, give only a brief handoff to `ios-accessibility`. |
| 9 | |
| 10 | ## Contents |
| 11 | |
| 12 | - [SwiftUI FocusState](#swiftui-focusstate) |
| 13 | - [Default Focus](#default-focus) |
| 14 | - [Focused Values and Scene Values](#focused-values-and-scene-values) |
| 15 | - [Focusable Interactions](#focusable-interactions) |
| 16 | - [Focus Sections](#focus-sections) |
| 17 | - [Focus Restoration](#focus-restoration) |
| 18 | - [UIKit Focus Guides](#uikit-focus-guides) |
| 19 | - [Common Mistakes](#common-mistakes) |
| 20 | - [Review Checklist](#review-checklist) |
| 21 | - [References](#references) |
| 22 | |
| 23 | ## SwiftUI FocusState |
| 24 | |
| 25 | Use `@FocusState` to read and write focus placement inside a scene. Use `Bool` for a single target or an optional `Hashable` enum for multiple targets. |
| 26 | |
| 27 | ```swift |
| 28 | struct LoginView: View { |
| 29 | enum Field: Hashable { case email, password } |
| 30 | |
| 31 | @State private var email = "" |
| 32 | @State private var password = "" |
| 33 | @FocusState private var focusedField: Field? |
| 34 | |
| 35 | var body: some View { |
| 36 | Form { |
| 37 | TextField("Email", text: $email) |
| 38 | .focused($focusedField, equals: .email) |
| 39 | |
| 40 | SecureField("Password", text: $password) |
| 41 | .focused($focusedField, equals: .password) |
| 42 | } |
| 43 | .onAppear { focusedField = .email } |
| 44 | .onSubmit { |
| 45 | switch focusedField { |
| 46 | case .email: focusedField = .password |
| 47 | case .password, nil: submit() |
| 48 | } |
| 49 | } |
| 50 | } |
| 51 | } |
| 52 | ``` |
| 53 | |
| 54 | Keep focus state local to the view that owns the focusable controls. |
| 55 | |
| 56 | ## Default Focus |
| 57 | |
| 58 | Use `.defaultFocus` to set the preferred initial focus region or control when a view appears or when focus is reassigned automatically. |
| 59 | |
| 60 | ```swift |
| 61 | struct SidebarView: View { |
| 62 | enum Target: Hashable { case library, settings } |
| 63 | @FocusState private var focusedTarget: Target? |
| 64 | |
| 65 | var body: some View { |
| 66 | VStack { |
| 67 | Button("Library") { } |
| 68 | .focused($focusedTarget, equals: .library) |
| 69 | |
| 70 | Button("Settings") { } |
| 71 | .focused($focusedTarget, equals: .settings) |
| 72 | } |
| 73 | .defaultFocus($focusedTarget, .library) |
| 74 | } |
| 75 | } |
| 76 | ``` |
| 77 | |
| 78 | Prefer one clear default destination per screen or focus region. |
| 79 | |
| 80 | ## Focused Values and Scene Values |
| 81 | |
| 82 | Use focused values to expose state from the currently focused view. Use scene-focused values when commands or scene-wide UI should keep access to the value even after focus moves within that scene. |
| 83 | |
| 84 | ```swift |
| 85 | struct SelectedRecipeKey: FocusedValueKey { |
| 86 | typealias Value = Binding<Recipe> |
| 87 | } |
| 88 | |
| 89 | extension FocusedValues { |
| 90 | var selectedRecipe: Binding<Recipe>? { |
| 91 | get { self[SelectedRecipeKey.self] } |
| 92 | set { self[SelectedRecipeKey.self] = newValue } |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | struct RecipeDetailView: View { |
| 97 | @Binding var recipe: Recipe |
| 98 | |
| 99 | var body: some View { |
| 100 | Text(recipe.title) |
| 101 | .focusedSceneValue(\.selectedRecipe, $recipe) |
| 102 | } |
| 103 | } |
| 104 | ``` |
| 105 | |
| 106 | Use this pattern for menus, commands, and toolbars that need to act on the focused scene's current content. |
| 107 | |
| 108 | ## Focusable Interactions |
| 109 | |
| 110 | Use `.focusable(_:interactions:)` on custom SwiftUI views that should participate in keyboard or directional focus. |
| 111 | |
| 112 | ```swift |
| 113 | struct SelectableCard: View { |
| 114 | let title: String |
| 115 | let action: () -> Void |
| 116 | @FocusState private var isFocused: Bool |
| 117 | |
| 118 | var body: some View { |
| 119 | Button(action: action) { |
| 120 | RoundedRectangle(cornerRadius: 12) |
| 121 | .fill(isFocused ? Color.accentColor.opacity(0.15) : .clear) |
| 122 | .overlay { Text(title) } |
| 123 | } |
| 124 | .buttonStyle(.plain) |
| 125 | .focusable(interactions: .activate) |
| 126 | .focused($isFocused) |
| 127 | } |
| 128 | } |
| 129 | ``` |
| 130 | |
| 131 | Prefer semantic `Button`, `Toggle`, `TextField`, and other system controls befor |