react-native-screens icon indicating copy to clipboard operation
react-native-screens copied to clipboard

[iOS] Native Stack layout shift, initial render doesn't take header into account

Open dylancom opened this issue 2 years ago • 8 comments
trafficstars

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
Scherm­afbeelding 2023-06-06 om 20 03 06 Scherm­afbeelding 2023-06-06 om 20 03 21

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

dylancom avatar Jun 06 '23 16:06 dylancom

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 }} />

dylancom avatar Jun 07 '23 09:06 dylancom