The problem I am having is while using a react-navigation
stack navigator nested inside a tab navigator for a basic chat UI appearance, the keyboard was hiding the chat message input field at the bottom. So I tried KeyboardAvoidingView
to bring the keyboard up to a visible position, but the keyboard wasn't showing. I have tried a solution that involves adding headerHeight
to the keyboardVerticalOffset
prop, but it seems to be about 50px off. For example, if I add headerHeight + 50
to keyboardVerticalOffset
everything looks great, but if I switched devices to an iPhone 5 or something, with a smaller screen, different SafeArea insets, etc, the keyboard would be in the wrong position again.
I am not sure what the culprit is exactly, but I am now thinking it's the SafeArea padding on top and/or bottom, which I have learned are the "insets". I am trying to use useSafeAreaInsets
, but all the values return 0! I want to use those insets to add to the keyboardVerticalOffset
prop so the avoiding view works properly.
I like the style of the tab bar right now, so I'd like to keep it with an increased height and padding and font size, but maybe I am doing it wrong with react native navigation? Maybe I cannot have this tab bar and stack navigator styling as I want with React Native? Regardless, I believe the insets should be returning a value, so I think that's where a problem lies.
Notice if I move the <SafeAreaView>
block to surround the <TouchableWithoutFeedback>
, instead of around the <NavigationContainer>
block, and remove the 50 extra pixels added to the keyboardVerticalOffset
, then the keyboard pushes the input field up properly, but the tab bar icons on iPhone 11 are squished. As I am writing this out, I am noticing that this change now has the bottom/topPadding variables returning values? If I then remove the tabBarOptions
, I get a basic appearance of the tabs that work, but I like the design of my initial tabs much more.
How do I maintain my current styling of the tab bar and have the keyboard avoid the chat input field on every device?
(Note: The useEffect
usage below is something I tried using the solution outlined in this issue: https://github.com/th3rdwave/react-native-safe-area-context/issues/54)
App.js:
import React, { Component, useEffect, useState } from 'react';
import { View, KeyboardAvoidingView, TextInput, Text, Platform, TouchableWithoutFeedback, Keyboard, ActivityIndicator, SafeAreaView, ScrollView, Button, StatusBar } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createStackNavigator, useHeaderHeight } from '@react-navigation/stack';
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
import { useSafeAreaInsets, useSafeAreaFrame } from 'react-native-safe-area-context';
import { Dimensions } from 'react-native';
const Stack = createStackNavigator();
function TicketStack() {
return (
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: "dodgerblue",
elevation: 0, // remove shadow on Android
shadowOpacity: 0, // remove shadow on iOS
},
headerTintColor: "#fff",
headerTitleStyle: {
fontWeight: "900",
fontSize: 26,
},
}}>
<Stack.Screen name="Ticket">
{(props) => <TicketScreen {...props} />}
</Stack.Screen>
<Stack.Screen name="Chat">
{(props) => <ChatScreen {...props} />}
</Stack.Screen>
</Stack.Navigator>
);
}
function HomeScreen() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text><FontAwesome5 name={"home"} size={20} color={"dodgerblue"} /> Home screen!</Text>
</View>
);
}
function TicketScreen(props){
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Ticket screen :)!</Text>
<Button title="Go to Chat" onPress={() => props.navigation.navigate('Chat')} />
</View>
);
}
function CustomKeyboardAvoidingView({ children, style }) {
const headerHeight = useHeaderHeight();
console.log("headerHeight: " + headerHeight)
console.log("StatusBar.currentHeight: " + StatusBar.currentHeight)
const insets = useSafeAreaInsets();
console.log("insets.top: " + topPadding)
console.log("insets.bottom: " + bottomPadding)
const [bottomPadding, setBottomPadding] = useState(insets.bottom)
const [topPadding, setTopPadding] = useState(insets.top)
useEffect(() => {
setBottomPadding(insets.bottom)
setTopPadding(insets.top)
console.log("topPadding: " + topPadding)
console.log("bottomPadding: " + bottomPadding)
}, [insets.bottom, insets.top])
// const frame = useSafeAreaFrame();
// const windowHeight = Dimensions.get('window').height
// console.log("frame.height: " + frame.height)
// console.log("windowHeight: " + windowHeight)
// const safeAreaHeight = windowHeight - frame.height
// console.log("safeAreaHeight: " + safeAreaHeight)
// safeAreaHeight is too much, needs to just be bottom or top padding from safearea
return (
<KeyboardAvoidingView
style={style}
behavior={Platform.OS == "ios" ? "padding" : "height"}
keyboardVerticalOffset={headerHeight + 50}
>
{children}
</KeyboardAvoidingView>
);
}
function ChatScreen(){
return(
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<CustomKeyboardAvoidingView style={{backgroundColor: "#fff", flex: 1, flexDirection: "column", justifyContent: "space-between" }}>
<View style={{backgroundColor: "dodgerblue", paddingVertical: 15}}>
<View style={{ margin: 10, marginBottom: 15}}>
<ActivityIndicator size="large" style={{marginBottom: 10}}/>
<Text>Waiting for more info here....</Text>
</View>
</View>
<ScrollView style={{backgroundColor: "tomato", paddingVertical: 15}}>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
<Text>Chat messages</Text>
</ScrollView>
<View style={{backgroundColor: "yellow", paddingVertical: 15}}>
<TextInput placeholder="Type your message here..." />
</View>
</CustomKeyboardAvoidingView>
</TouchableWithoutFeedback>
)
}
const Tab = createBottomTabNavigator();
// TODO:
// - removing safeareaview makes tabs squished and icon nearly invisible, but chat message input fields avoids keyboard properly (squished can be fixed by removing tabBarOptions)
// - having safeareaview makes tabs look good, but chat message input field is hidden by keyboard
// - safeareainsets? why are they 0? i would be adding the bottom or top padding of insets to the vertical offset of the keyboard avoiding view.
export default class App extends Component {
render(){
return (
<SafeAreaView style={{flex: 1, backgroundColor: "dodgerblue"}}>
<NavigationContainer>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ color, size }) => {
let iconName;
if (route.name === 'Home') {
iconName = 'home';
} else if (route.name === 'Ticket') {
iconName = 'question';
}
return <FontAwesome5 name={iconName} size={size} color={color} />;
},
})}
tabBarOptions={{
style: {
height: 70
},
activeTintColor: "#fff",
inactiveTintColor: "dodgerblue",
inactiveBackgroundColor: "#fff",
activeBackgroundColor: "dodgerblue",
tabStyle: {
paddingTop: 10,
paddingBottom: 10
},
labelStyle: {
fontSize: 14
},
}}>
<Tab.Screen name="Home">
{(props) => <HomeScreen {...props} />}
</Tab.Screen>
<Tab.Screen name="Ticket">
{(props) => <TicketStack {...props} />}
</Tab.Screen>
</Tab.Navigator>
</NavigationContainer>
</SafeAreaView>
);
}
}
package.json:
{
"name": "ReactNativeTest",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"lint": "eslint ."
},
"dependencies": {
"@react-native-community/masked-view": "^0.1.10",
"@react-navigation/bottom-tabs": "^5.9.2",
"@react-navigation/native": "^5.7.6",
"@react-navigation/stack": "^5.9.3",
"react": "16.13.1",
"react-native": "0.63.3",
"react-native-gesture-handler": "^1.8.0",
"react-native-reanimated": "^1.13.1",
"react-native-safe-area-context": "^3.1.8",
"react-native-screens": "^2.11.0",
"react-native-vector-icons": "^7.1.0"
},
"devDependencies": {
"@babel/core": "7.11.6",
"@babel/runtime": "7.11.2",
"@react-native-community/eslint-config": "1.1.0",
"babel-jest": "25.5.1",
"eslint": "6.8.0",
"jest": "25.5.4",
"metro-react-native-babel-preset": "0.59.0",
"react-test-renderer": "16.13.1"
},
"jest": {
"preset": "react-native"
}
}
So as Alex over at this Github issue pointed out, wrapping the app in
SafeAreaProvider
fixes the insets not returning values properly.The Tab bar was still squished with
SafeAreaView
wrapping theNavigationContainer
and I fixed that by adding a height of 70 totabStyle
prop ofTab.Navigator
. Then another issue arose, where the Stack Header of the TicketScreen was way too large and was fixed from this Github issue by addingheaderStatusBarHeight: 0
toStack.Navigator
screenOptions
prop. The insets were only returning properly insideuseEffect
with asetState
usage, which I then used thetopPadding
value to add to thekeyboardVerticalOffset
prop and the keyboard was popping up without hiding the input field on an iPhone 11 and iPhone 6s!Update note: Make sure to also set
keyboardHidesTabBar: (Platform.OS == "ios" ? false : true)
as a prop onTab.Navigator
.Here's the full working code: