react-native-screens
react-native-screens copied to clipboard
[iOS] Native Stack layout shift, initial render doesn't take header into account
Description
On initial render the View within a Native Stack has an incorrect height. Upon a second render the height is corrected by taking the header into account. This causes a layout jump/shift which becomes noticable if you are vertically centering your content. Sometimes rendering happens so fast that you don't see it but in other cases it's noticable.
Caused by vc.edgesForExtendedLayout = UIRectEdgeNone; being applied after the first render.
(introduced by: https://github.com/software-mansion/react-native-screens/pull/222/files)
On for e.g. an iPhone 14 Pro the initial height is 773, which becomes 675.3333129882812 after taking into account "UIRectEdgeNone".
Expected behavior: no layout shift. The View should directly get a correct height or only be shown after the corrected height was calculated.
| First render (vertically centered incorrectly) | Second render |
|---|---|
Steps to reproduce
import * as React from 'react';
import { Button, Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
function HomeScreen({ navigation }) {
const [initialHeight, setInitialHeight] = React.useState(0);
const [correctedHeight, setCorrectedHeight] = React.useState(0);
return (
<View
onLayout={event => {
const {x, y, width, height} = event.nativeEvent.layout;
if (!initialHeight) {
setInitialHeight(height)
} else {
setCorrectedHeight(height)
}
}}
style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text style={{ fontSize: 20, lineHeight: 20 * 1.6, textAlign: 'center' }}>
Initial height: <Text style={{ fontWeight: 'bold' }}>{initialHeight}</Text>{`\n`}
Corrected height: <Text style={{ fontWeight: 'bold' }}>{correctedHeight}</Text>
</Text>
</View>
);
}
const Stack = createNativeStackNavigator();
function MyStack() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
);
}
export default function App() {
return (
<NavigationContainer>
<MyStack />
</NavigationContainer>
);
}
Snack or a link to a repository
https://snack.expo.dev/zxQpf-O7e?platform=ios
Screens version
3.20.0
React Native version
0.71.6
Platforms
iOS
JavaScript runtime
Hermes
Workflow
React Native (without Expo)
Architecture
Paper (Old Architecture)
Build type
Debug mode
Device
iOS simulator
Device model
iPhone 14 Pro
Acknowledgements
Yes
Temporary hack: To prevent the layout from jumping, I currently calculate the screens height using:
const {height: WINDOW_HEIGHT} = Dimensions.get('window');
const headerHeight = useHeaderHeight();
const bottomTabBarHeight = useBottomTabBarHeight();
const screenHeight = WINDOW_HEIGHT - headerHeight - bottomTabBarHeight;
<View style={{ maxHeight: screenHeight }} />