swift-focusengine-pro
swift focusengine pro
SKILL.md
Full skill instructions
Review focus management code for correctness, modern API usage, and adherence to Apple's focus engine rules. Covers all Apple platforms. Report only genuine problems — do not nitpick or invent issues.
Review process:
- Check for critical anti-patterns using
references/anti-patterns.md. - Determine the target platform and load the appropriate references:
- tvOS:
references/swiftui-focus.mdandreferences/uikit-focus.md. - iOS/iPadOS:
references/ios-focus.md(focus groups, halo, keyboard nav). - watchOS:
references/watchos-focus.md(Digital Crown, sequential focus). - visionOS:
references/visionos-focus.md(gaze, hover effects) andreferences/realitykit-focus.md(RealityKit entities, gestures, volumes). - macOS:
references/macos-focus.md(key view loop, focus ring, NSView focus, focusedValue for menus, Mac Catalyst). - For cross-platform: load all relevant references.
- tvOS:
- Check focus styling and visual feedback using
references/focus-styling.md. - Verify focus restoration and data reload handling using
references/focus-restoration.md. - Audit layout patterns for focus section isolation using
references/layout-patterns.md. - Check async/await and data loading focus patterns using
references/async-focus.md. - Verify accessibility integration using
references/accessibility-focus.md. - Check debugging and testing practices using
references/debugging.md.
If doing a partial review, load only the relevant reference files.
Core Instructions
tvOS
- tvOS uses a focus-based navigation model — every interactive element must be reachable via the Siri Remote's directional pad.
- Focus movement is purely geometric — the focus engine draws a rectangle from the currently focused view in the swipe direction and picks the nearest focusable view in that rectangle.
- If nothing is in the geometric path, focus does not move. Period. Use
.focusSection()(SwiftUI) orUIFocusGuide(UIKit) to bridge gaps. - Never use
.disabled()on tvOS to toggle interactivity — it removes views from the focus chain entirely..allowsHitTesting(false)is unreliable on tvOS (may map toisUserInteractionEnabled = false). Preferred: gate the action inside the button closure, or use the dual@FocusState+.disabled()gating pattern for lists (anti-pattern #25). prefersDefaultFocus(_:in:)does NOT work insideScrollViewon tvOS — usedefaultFocus(_:_:priority:)instead. Note:defaultFocuswith.userInitiatedonly fires on initial appearance, NOT on every re-entry.- Prefer
ScrollPositionoverScrollViewReader.scrollTo()— imperative scrollTo creates feedback loops with the focus engine (anti-pattern #26). - Always test on real Apple TV hardware — Simulator focus behavior differs.
iOS/iPadOS
- Focus is a secondary interaction model — it only activates with a hardware keyboard connected.
- Tab moves between focus groups; arrow keys move within a group. This two-level model does NOT exist on tvOS.
- Use
focusGroupIdentifier(iOS 15+, UIKit) to define custom focus groups — this API is NOT available on tvOS. - Use
UIFocusHaloEffectto customize the system focus ring — NOT available on tvOS. - Set
allowsFocus = trueandselectionFollowsFocus = trueon collection/table views for keyboard navigation. - Your app must work perfectly without keyboard focus — always test with touch only.
watchOS
- Focus routes Digital Crown input to the correct view — shown by a green border.
- Focus is sequential (layout order), NOT spatial/directional.
.focusSection()is NOT available on watchOS..focusable()MUST come BEFORE.digitalCrownRotation()— reversing silently breaks crown input.- Do NOT add
.focusable()to system controls (Picker, Stepper, Toggle) — they already handle it.
visionOS
- Eye gaze = hover targeting, NOT focus.
onHover(perform:)does NOT fire from gaze — only from pointer devices. - Use
.hoverEffect()for gaze visual feedback. System controls get it automatically; custom views need it explicitly. @FocusStateonly activates with keyboard (Magic Keyboard), VoiceOver, or Switch Control.- RealityKit entities need
InputTargetComponent+CollisionComponent+HoverEffectComponentfor gaze interaction. .focusEffectDisabled()hides keyboard focus ring;.hoverEffectDisabled()disables gaze hover — they are different.
macOS
- macOS uses a key view loop — Tab/Shift-Tab moves between views in a defined sequence. This is NOT spatial like tvOS.
- Custom NSView subclasses must override
acceptsFirstResponderto returntrue— the default isfalse, making the view invisible to Tab navigation. recalculatesKeyViewLoop = trueon NSWindow overwrites all manualnextKeyViewconnections. Pick one approach.- Focus ring customization: override
focusRingType,focusRingMaskBounds, anddrawFocusRingMask()on NSView. focusedValue/focusedSceneValueare critical on macOS for making menu bar commands respond to the current selection.- Mac Catalyst: inherits iPad
UIFocusSystem. If the iPad app doesn't support keyboard focus, the Catalyst app won't either. - Full Keyboard Access is OFF by default — most users only Tab between text fields and lists, not all controls.
All Platforms
- Never add
.focusable()to Buttons or NavigationLinks — they are already focusable. Adding it creates a double-focus wrapper. - Do not mix
@FocusState(SwiftUI) and UIKit focus APIs (setNeedsFocusUpdate) on the same view hierarchy branch. - VoiceOver focus (
@AccessibilityFocusState) is completely separate from UI focus (@FocusState).
Output Format
Organize findings by file. For each issue:
- State the file and relevant line(s).
- Name the rule being violated (e.g., "Use
.allowsHitTesting(false)instead of.disabled()"). - Show a brief before/after code fix.
Skip files with no issues. End with a prioritized summary of the most impactful changes to make first.
Example output:
TopicsView.swift
Line 49: .disabled() removes view from tvOS focus chain (anti-pattern #1).
// Before
TopicClipsGridView(...)
.disabled(!wrapper.isGridFocusable)
// After — gate the action, not the view
TopicClipsGridView(...)
.opacity(wrapper.isGridFocusable ? 1.0 : 0.5)
// Move the guard inside the button/action closures instead
Line 72: Missing .focusSection() on horizontal ScrollView in vertical layout.
// Before
ScrollView(.horizontal) {
HStack { /* row items */ }
}
// After
ScrollView(.horizontal) {
HStack { /* row items */ }
}
.focusSection()
Summary
- Focus breakage (critical):
.disabled()on line 49 removes grid from focus chain entirely. - Focus jumping (high): Missing
.focusSection()causes cross-row focus jumps.
End of example.
References
references/anti-patterns.md— Critical mistakes that break focus navigation: 14 tvOS + 7 macOS-specific anti-patterns.references/swiftui-focus.md— SwiftUI focus APIs: @FocusState, focusSection, prefersDefaultFocus, focused, defaultFocus, onMoveCommand.references/uikit-focus.md— UIKit focus APIs: UIFocusEnvironment, UIFocusGuide, shouldUpdateFocus, didUpdateFocus, preferredFocusEnvironments, UIFocusDebugger.references/focus-styling.md— Focus visual feedback: ButtonStyle with isFocused, FocusBorder, hover effects, scale/shadow animations, macOS focus ring styling.references/focus-restoration.md— Handling focus after data reloads, navigation, and async updates.references/layout-patterns.md— Common tvOS layouts: table-of-collections, sidebar+content, tab bar, horizontal shelves.references/ios-focus.md— iOS/iPadOS-specific: focus groups, focusGroupIdentifier, UIFocusHaloEffect, keyboard navigation, allowsFocus, selectionFollowsFocus.references/watchos-focus.md— watchOS-specific: Digital Crown routing, sequential focus, digitalCrownRotation, focusable ordering.references/visionos-focus.md— visionOS-specific: gaze vs focus vs hover, HoverEffect, HoverEffectGroup, RealityKit HoverEffectComponent, spatial input.references/macos-focus.md— macOS-specific: key view loop, NSView focus (acceptsFirstResponder, canBecomeKeyView), focus ring customization, focusedValue for menus, Mac Catalyst, Full Keyboard Access.references/realitykit-focus.md— RealityKit entity hover: HoverEffectComponent, collision shapes, gestures, shader effects, mixed SwiftUI+RealityKit hierarchies.references/async-focus.md— Async focus patterns: @MainActor coordination, focus after data load, NavigationStack pop, Task cancellation, debouncing.references/accessibility-focus.md— Accessibility integration: @AccessibilityFocusState, VoiceOver + focus, Full Keyboard Access, Switch Control, Reduce Motion.references/debugging.md— UIFocusDebugger, _whyIsThisViewNotFocusable, launch arguments, Quick Look, macOS first responder debugging.
