Ensuring Content Stays Within a Mobile Device's Safe Area
4 min read
For this week's blog post, I want to give a shoutout to a handy little package called the react-native-safe-area-context. When I first started developing for mobile with React Native, I was let loose on an existing code base, and so, never really had to think about how to ensure that any content displayed to users falls within the mobile device's "safe area".
But what does "safe area" mean really? React Native actually comes with a SafeAreaView
component whose purpose is to "render content within the safe area boundaries of a device".
SafeAreaView
renders nested content and automatically applies padding to reflect the portion of the view that is not covered by navigation bars, tab bars, toolbars, and other ancestor views. Moreover, and most importantly, Safe Area's paddings reflect the physical limitation of the screen, such as rounded corners or camera notches (i.e. the sensor housing area on iPhone X).
As you can imagine, in today's world where there are hundreds, if not thousands, of devices available, this is an absolute must, only to be ignored at your own peril. ☠️ Unfortunately, as of React Native v0.66, SafeAreaView
is only applicable to iOS devices with iOS version 11+. So what's a React Native developer to do?
Enter react-native-safe-area-context
! This is a package that introduces itself as "a flexible way to handle safe area, [that] also works on Android and Web!". Hurrah! Because our app needs to cater for Android users in particular, this is a package we've implemented in our code base with good success.
Here's a quick run-through of how to set it up.
- Wrap your root component, in the
SafeAreaProvider
.
import { SafeAreaProvider } from 'react-native-safe-area-context'
function App() {
return <SafeAreaProvider>...</SafeAreaProvider>
}
- Wrap the contents of your screen component in a
SafeAreaView
. In my case, I use aScreen
component which serves as the basis for all the screens across the app. This is therefore where I chose to add theSafeAreaView
. Note that this component accepts all props that a React NativeView
component would accept.
import { SafeAreaView } from 'react-native-safe-area-context'
export function Screen(props) {
return (
<SafeAreaView style={{ flex: 1, backgroundColor: 'red' }}>
<View style={{ flex: 1, backgroundColor: 'blue' }} />
</SafeAreaView>
)
}
And that's pretty much it really! There are more settings and API features you can play with, so check out their GitHub page for more info.
Now, the reason this all came to my attention recently was because I was having some spacing issues upon deciding to implement a tab navigator, for the first time, in the app. This tab navigator will sit at the bottom of the screen, and I had set it so that screen content would not "flow beneath" the tab, but instead stop above the tab. (If you use React Navigation bottom tabs navigator, and you're curious about the setting, it's this one.)
The SafeAreaView
sets the margins nicely for all edges of the mobile screen... apart from the bottom edge. Why? SafeAreaView
(of course) does not know that there's now a tab navigator in between the device's edge and the bottom of the screen's content, therefore resulting in a large gap between the end of the screen's content and the top of the tab navigator.
Thankfully, there's a nice easy fix that comes with the react-native-safe-area-context
package, through the use of the edges
prop. This tells SafeAreaView
which edges you want to apply the safe area margins to, which in my case, meant excluding the "bottom" edge. However, because I use the Screen
component as the basis for all my screens, I had to be sure this only applied to screens that sit within the tab navigator view.
import { SafeAreaView } from 'react-native-safe-area-context'
const edgesTabView = ['right', 'top', 'left']
export function Screen(props) {
return (
<SafeAreaView style={{ flex: 1, backgroundColor: 'red' }} edges={props.inTabView ? edgesTabView : undefined}>
<View style={{ flex: 1, backgroundColor: 'blue' }} />
</SafeAreaView>
)
}
As you can see here, whenever I use my Screen
component, I can set an inTabView
prop, where if true
, will only set safe area insets for the right, top and left screen edges. If edges
is not defined, it defaults to all edges.