While developing TV apps using react-native-tvos, you will notice differences in behaviour between platforms like tvOS and Android TV.
One such difference is in navigation.
In this blog, we will explore navigation issues and learn how to solve them.
TV apps are navigable using a remote control with a D-pad, which has four directional buttons: up, down, left, and right.
Both Android and tvOS have their native focus engines that determine the next element to focus on based on the current state and user input. However, their behaviours differ significantly.
In Android TV, there is a built-in focus engine that operates based on "proximity." This engine selects the next focusable element closest to the current focus, even if it doesn't align with the current element, which can lead to confusing and unexpected focus behavior 😤.
Please read this to learn in detail how navigation works on both platforms.
In contrast, tvOS works on precision. It only changes the focus when there is an element directly next to the current one. Again, this can lead to confusing and unexpected focus behavior 😤.
In short, there are two main issues with the native focus engines of Android TV and tvOS at a higher level:
❓ What is the solution?
One way to solve this to use TVFocusGuideView
🎉🎉 .
TVFocusGuideView: This component provides support for Apple’s UIFocusGuide API, to help ensure that focusable controls can be navigated to, even if they are not directly in line with other controls. As per the React Native TVOS
React Native TVOS has TVFocusGuideView
functionality is out of the box.
💻 How to use TVFocusGuideView?
TVFocusGuideView
is default integrated in the React Native TVOS. TVFocusGuideView has a few props that helps in managing the focus.
What will we be building?
To understand TVFocusGuideView
and the use of its props, we will create an app. Please refer to the image below.
Lets have a look at some scenarios based on some typical user requirements. While implementing these requirements, we will learn usage of different props, and implementations of the TVFocusGuideView component.
🥴 What if we don't use TVFocusGuideView
If we don't use TVFocusGuideView
, there’s no control on the focus & navigation and the above requirements will work like this:
Clicking the RIGHT button on the D-Pad will shift the focus to the first “touchable” element, typically within the grid.
Clicking the LEFT button on the D-Pad will direct focus to the side navigation
Pressing the DOWN button on the D-Pad will move focus outside the grid, and so forth.
🥂 With TVFocusGuideView
👩💻 Let's code
Let's start implementing all the requirements we discussed in "What we will be building?".
1. autoFocus
autoFocus
is to programmatically managing the focus from one element to another.
Eg: on click of RIGHT Button, Focus should go to the hero image of main section from Sub Nav (Home button).
Without TVFocusGuideView
's autoFocus
the focus would skip the Hero Image and go to the the grid.
Why? Because focus works on “proximity algorithm”.
To move the focus from side nav to the hero image, we can use autoFocus
from tvFocusGuideView
.
- Wrap the Hero component with
tvFocusGuideView
- Use
autoFocus
props on the top most wrapper
import React from 'react';
import {Text, TouchableOpacity} from 'react-native';
const Hero = () => {
return (
<TVFocusGuideView autoFocus>
<Text >This is hero Image</Text>
<TouchableOpacity>
<Text>Play Now</Text>
</TouchableOpacity>
</TVFocusGuideView>
);
};
export default Hero;
b. Another example of autoFocus
<TVFocusGuideView autoFocus>
<Hero />
<Grid />
</TVFocusGuideView>
2. trapFocus
trapFocus
ensures the focus does not move or escape.
(Please, refer the below image) when LEFT button is pressed on the 1st and 5th box, the focus should remain in grid.
For this we need trapFocusLeft
, if we don’t use trapFocusLeft
, the focus will go to navigation from Grid.
- Wrap the Grid component with
tvFocusGuideView
. - Use
trapFocusLeft
props withtvFocusGuideView
.
import React from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
const Grid = () => {
return (
<View>
<TVFocusGuideView trapFocusLeft>
<View>
<TouchableOpacity>
<Text>1</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>2</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>3</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>4</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>5</Text>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity>
<Text>6</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>7</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>8</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>9</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>10</Text>
</TouchableOpacity>
</View>
</TVFocusGuideView>
<View>
<TouchableOpacity>
<Text>Outside Button</Text>
</TouchableOpacity>
</View>
</View>
);
};
export default Grid;
b. One more example of trapFocus
, on click of RIGHT button on D-pad, the focus should not go to the main section and stays at the side navigation.
We want user to move to main section only after reaching at the end of the navigation.
PS: this may not be a great CX but this is just to showcase the use of trapFocus.
To stop the focus moving to the main section we will use trapFocusLeft
props of TVFocusGridView
In the provided code, we have wrapped our Nav component with TVFocusGuideView
and added the props trapFocusRight
.
Now , on click of RIGHT button on D-pad, the focus will not move to the main section. Instead, it will remain inside the Nav . By using DOWN button we can navigate to all items within the Nav
import React from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
const Nav= () => {
return (
<TVFocusGuideView trapFocusRight>
<View>
<TouchableOpacity>
<Text>Home</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>About</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>Live</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>Settings</Text>
</TouchableOpacity>
</View>
</TVFocusGuideView>
);
};
export default Nav;
3. destinations
To override default previous destinations, you can use destinations
props.
Eg: On click of Left button , move focus from hero Image to “about” and not to “settings”.
In the below code, we have done the following:
- Wrapped the Nav component with
TVFocusGuideView
- Utilised
useRef
, to obtain the reference of items. - Utilised
destinations
props to define the previous elements. -- - - Remember, since we are usingrefs
, we need to reference them byabout.current
- Test time. Now, press back from hero image and you will observe that focus is going to About and not to the last focused element settings
import React, {useRef} from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
const Nav= () => {
const about = useRef();
return (
<TVFocusGuideView
trapFocusRight
destinations={[about.current]}>
<View>
<TouchableOpacity >
<Text>Home</Text>
</TouchableOpacity>
<TouchableOpacity ref={about} style={styles.nav}>
<Text>About</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>Live</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>Settings</Text>
</TouchableOpacity>
</View>
</TVFocusGuideView>
);
};
export default Nav;
📚 Resources
⭐ Summary:
Navigation Differences: There are differences in navigation behavior between tvOS and Android TV when developing TV apps with react-native-tvos.
Focus Engines: Android TV uses a proximity-based focus engine and tvOS uses a precision-based focus engine, both leading to confusing behaviors.
Solution:
TVFocusGuideView
as a solution for better focus management, supporting Apple’s UIFocusGuide API.props:
TVFocusGuideView
's props haveautoFocus
,trapFocus
,destinations
.Benefits: TVFocusGuideView provides more controlled and predictable navigation, improving user experience.
Happy Learning!!
If you like this blog, please share it with your friends and drop a comment. If you found anything that could be improved, please reach out to me on X and Linkedin.
Top comments (3)
Thankyou @hellonehha, this was such an easy to digest explanation of such a complicated topic!
Thank you so much for this post. this post help me a lot
amazing!!