Agent skill
apple-tv-troubleshooter
Expert troubleshooting for Apple TV (tvOS) React Native development. Use when users have issues with Siri Remote, focus management, TVEventHandler, TVFocusGuideView, ScrollView not scrolling, tvOS-specific problems, parallax animations, or tvOS vs Android TV differences.
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/apple-tv-troubleshooter
SKILL.md
Apple TV Troubleshooter
You are an expert in Apple TV (tvOS) development with React Native. This skill activates when users encounter:
- Focus management issues on Apple TV
- Siri Remote event handling problems
- TVEventHandler not capturing events
- ScrollView/FlatList not scrolling
- TVFocusGuideView configuration
- tvOS vs Android TV differences
- Expo TV build issues
- Navigation and focus traps
tvOS Focus Engine vs Android TV
Critical Difference: tvOS uses a precision-based focus engine while Android TV uses proximity-based.
| Aspect | Apple TV (tvOS) | Android TV |
|---|---|---|
| Focus Engine | Precision-based (strict alignment) | Proximity-based (nearest element) |
| Remote Input | Siri Remote touchpad (swipe + click) | D-pad directional buttons |
| Focus Recovery | Attempts automatic (inconsistent) | Moves to top-left corner |
| Screen Resolution | 1920x1080 (native) | 960x540 (scaled) |
Implication: UI elements must be properly aligned on tvOS or focus won't move between them.
Siri Remote Event Handling
Using useTVEventHandler Hook (Recommended)
typescript
import { useTVEventHandler } from 'react-native';
function MyComponent() {
useTVEventHandler((evt) => {
switch (evt.eventType) {
case 'up':
case 'down':
case 'left':
case 'right':
// Handle navigation
break;
case 'select':
// Center button pressed
break;
case 'playPause':
// Play/Pause button
break;
case 'longPlayPause':
// Long press play/pause (tvOS only)
break;
}
});
return <View>{/* content */}</View>;
}
TVEventControl for Menu and Gestures
typescript
import { TVEventControl } from 'react-native';
// Enable Menu button handling (for back navigation)
TVEventControl.enableTVMenuKey();
// Enable pan gesture detection on Siri Remote touchpad
TVEventControl.enableTVPanGesture();
// Disable when component unmounts
TVEventControl.disableTVMenuKey();
TVEventControl.disableTVPanGesture();
Common Problems & Solutions
| Problem | Cause | Solution |
|---|---|---|
| ScrollView won't scroll | Regular ScrollView needs focusable items | Use TVTextScrollView for swipe-based scrolling |
| TVEventHandler doesn't fire | No focusable component on screen | Add hasTVPreferredFocus={true} to parent View or ensure a Touchable exists |
| Event fires twice | Press and release both trigger | Known behavior - debounce or track event state |
| InputText can't receive focus | tvOS limitation | Use native input alternatives or custom keyboards |
| Focus leaves FlatList unexpectedly | Virtualization removes focused item | VirtualizedList auto-wraps with TVFocusGuideView - ensure trapFocus enabled |
| Menu button doesn't work | Not enabled by default | Call TVEventControl.enableTVMenuKey() |
| Pan/swipe not detected | Disabled by default | Call TVEventControl.enableTVPanGesture() |
| Expo prebuild fails after changing EXPO_TV | Cached native config | Always run npx expo prebuild --clean |
| Flipper causes build errors | Incompatible with TV | Set Flipper to false in Podfile, run prebuild --clean |
| Wrong screen dimensions | Platform difference | Use platform-specific StyleSheets |
| Focus doesn't move diagonally | Precision engine limitation | Ensure UI elements are aligned vertically/horizontally |
| BackHandler doesn't work | Different API on tvOS | Use TVEventControl.enableTVMenuKey() for menu/back |
| Parallax not working | Missing props | Add tvParallaxProperties to TouchableHighlight |
| removeClippedSubviews breaks focus | Clipped items lose focus | Set removeClippedSubviews={false} |
TVFocusGuideView Configuration
typescript
import { TVFocusGuideView } from 'react-native';
// Basic usage with auto-focus memory
<TVFocusGuideView autoFocus>
<TouchableOpacity>Item 1</TouchableOpacity>
<TouchableOpacity>Item 2</TouchableOpacity>
</TVFocusGuideView>
// Trap focus within container
<TVFocusGuideView
trapFocusUp
trapFocusDown
trapFocusLeft
trapFocusRight
>
{/* Focus cannot escape this container */}
</TVFocusGuideView>
// Custom focus destinations
<TVFocusGuideView destinations={[buttonRef.current]}>
{/* Guides focus to specific elements */}
</TVFocusGuideView>
Key Props
| Prop | Description |
|---|---|
autoFocus |
Remembers last focused child, restores on revisit |
trapFocusUp/Down/Left/Right |
Prevents focus from leaving in that direction |
destinations |
Array of refs to guide focus toward |
focusable |
When false, view and children not focusable |
Platform-Specific Components
TVTextScrollView (for scrolling content)
typescript
import { TVTextScrollView } from 'react-native';
// Use instead of ScrollView for non-focusable content
<TVTextScrollView>
<Text>Long text content that should scroll with swipe...</Text>
</TVTextScrollView>
Parallax Animations
typescript
<TouchableHighlight
tvParallaxProperties={{
enabled: true,
magnification: 1.1,
pressMagnification: 1.0,
shiftDistanceX: 5,
shiftDistanceY: 5,
}}
>
<Image source={poster} />
</TouchableHighlight>
Unsupported Components on tvOS
These components are disabled or suppressed on Apple TV:
StatusBarSliderSwitchWebView(limited support)
Focus Management Best Practices
1. Set Default Focus on Mount
typescript
<TouchableOpacity hasTVPreferredFocus={true}>
Default Focused Item
</TouchableOpacity>
2. Use nextFocus Props for Custom Navigation
typescript
<TouchableOpacity
ref={button1Ref}
nextFocusRight={button2Ref.current}
nextFocusDown={button3Ref.current}
>
Button 1
</TouchableOpacity>
3. Capture Events at Top Level
typescript
// Good: Capture at parent level
function Screen() {
useTVEventHandler((evt) => {
// Handle all events here, delegate to children
});
return <View>{/* children */}</View>;
}
// Bad: Each small component handles its own events
function SmallButton() {
useTVEventHandler((evt) => { /* ... */ }); // Avoid this pattern
}
4. Use React Context for Focus State
typescript
const FocusContext = createContext({ focusedId: null, setFocused: () => {} });
function FocusProvider({ children }) {
const [focusedId, setFocused] = useState(null);
return (
<FocusContext.Provider value={{ focusedId, setFocused }}>
{children}
</FocusContext.Provider>
);
}
Expo TV Specific Issues
Environment Variable
bash
# Must be set BEFORE prebuild
export EXPO_TV=1
# Always clean when changing this variable
npx expo prebuild --clean
Common Expo TV Errors
| Error | Solution |
|---|---|
| "EXPO_TV not recognized" | Ensure using Expo SDK 50+ |
| Build fails after toggling EXPO_TV | Run npx expo prebuild --clean |
| Flipper errors | Disable Flipper in ios/Podfile |
| Dev menu not showing | Use SDK 54+ with RNTV 0.81 for TV dev menu support |
Platform Detection
typescript
import { Platform } from 'react-native';
// Check if running on any TV
if (Platform.isTV) {
// TV-specific code
}
// Check specifically for Apple TV (not Android TV)
if (Platform.isTVOS) {
// Apple TV only code
}
// Platform-specific styles
const styles = StyleSheet.create({
container: {
padding: Platform.isTVOS ? 48 : 16,
},
});
Debugging Tips
- LogBox works on TV - Error display supported after RN TV 0.76+
- Use console.log liberally - Metro bundler shows logs
- Test on real device - Simulator misses Siri Remote nuances
- Check focus state - Add
onFocus/onBlurhandlers to debug focus flow
Resources
Didn't find tool you were looking for?