Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ios onLayout height & screen content jumping on initial render #1504

Open
3 of 12 tasks
Gongreg opened this issue Jul 1, 2022 · 7 comments
Open
3 of 12 tasks

ios onLayout height & screen content jumping on initial render #1504

Gongreg opened this issue Jul 1, 2022 · 7 comments
Labels
Missing info The user didn't precise the problem enough Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided

Comments

@Gongreg
Copy link

Gongreg commented Jul 1, 2022

Duplicated from react-navigation/react-navigation#10673 since the bot asked for it.

Description

Current behavior

Using Native Stack navigator and navigating to another screen, which has onLayout listener for the Root element logs:

(tested on iphone XS max)

►{width:414,height:896,x:0,y:0}
►{width:414,height:808,x:0,y:0}

If I wrap the screen with SafeAreaView, it logs even more onLayoutEvents:

►{width:414,height:896,x:0,y:0}
►{width:414,height:818,x:0,y:44}
►{width:414,height:730,x:0,y:44}

On top of it, on some ios devices I can see that the content gets rendered with the initial height (896) & quickly rerendered with the correct height (the components don't have hardcoded height inside but use flex).

I think the layout jumping depends on JS execution speed (it happens on a production build, but does not happen if enable debug build or do console.warn).

Here is an image showing that content initially gets rendered bigger and then gets to correct height:

HEIGHT 896 HEIGHT 808
image image

Expected behavior

The onLayout should be called with correct layout info the very first time & the screen should not jump on the very first render.

Steps To Reproduce

Reproduction

https://snack.expo.dev/wMSEvt-Vr

Platform

  • Android
  • iOS
  • Web
  • Windows
  • MacOS

Packages

  • @react-navigation/​bottom-tabs
  • @react-navigation/​drawer
  • @react-navigation/​material-bottom-tabs
  • @react-navigation/​material-top-tabs
  • @react-navigation/​stack
  • @react-navigation/​native-stack

Environment

  • I've removed the packages that I don't use
package version
@react-navigation/native 6.0.10
@react-navigation/native-stack 6.6.2
react-native-safe-area-context 4.3.1
react-native-screens 3.14.0
react-native expo version (also happens on 0.63)
expo latest
@github-actions
Copy link

github-actions bot commented Jul 1, 2022

Hey! 👋

It looks like you've omitted a few important sections from the issue template.

Please complete Description and Steps To Reproduce sections.

@github-actions github-actions bot added Missing info The user didn't precise the problem enough Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided labels Jul 1, 2022
@RickardZrinski
Copy link

RickardZrinski commented Nov 2, 2022

I can confirm that the same issue happens on Android as well. The issue is due to the presence of a screen header.

With a header

LOG Header height: 91
LOG Header height: 91
LOG Screen onLayout {"height": 844, "width": 390, "x": 0, "y": 0}
LOG Screen onLayout {"height": 753, "width": 390, "x": 0, "y": 0}
LOG Header height: 91
LOG Header height: 91

The header height is determined from React Navigation's useHeaderHeight. onLayout is on the screen's root view.

The difference in the screen's height between the onLayout calls is the exact height of the header (844-753=91).

It's odd because the header height remains static as seen by the output from useHeaderHeight. It's 91 before onLayout and 91 after onLayout.

Without a header

LOG Header height: 0
LOG Header height: 0
LOG Screen onLayout {"height": 844, "width": 390, "x": 0, "y": 0}
LOG Header height: 0
LOG Header height: 0

The headerShown: false option is passed in the StackNavigator.Screen's options.

As you can see the onLayout is triggered twice with a header and only once without a header.

The issue is exclusive to @react-navigation/native-stack. @react-navigation/stack doesn't have the same issue.

@flygomel
Copy link

flygomel commented Dec 4, 2022

same issue

@flygomel
Copy link

flygomel commented Dec 4, 2022

I can confirm that the same issue happens on Android as well. The issue is due to the presence of a screen header.

With a header

LOG Header height: 91
LOG Header height: 91
LOG Screen onLayout {"height": 844, "width": 390, "x": 0, "y": 0}
LOG Screen onLayout {"height": 753, "width": 390, "x": 0, "y": 0}
LOG Header height: 91
LOG Header height: 91

The header height is determined from React Navigation's useHeaderHeight. onLayout is on the screen's root view.

The difference in the screen's height between the onLayout calls is the exact height of the header (844-753=91).

It's odd because the header height remains static as seen by the output from useHeaderHeight. It's 91 before onLayout and 91 after onLayout.

Without a header

LOG Header height: 0
LOG Header height: 0
LOG Screen onLayout {"height": 844, "width": 390, "x": 0, "y": 0}
LOG Header height: 0
LOG Header height: 0

The headerShown: false option is passed in the StackNavigator.Screen's options.

As you can see the onLayout is triggered twice with a header and only once without a header.

The issue is exclusive to @react-navigation/native-stack. @react-navigation/stack doesn't have the same issue.

this is working, thank you

@maorgershman
Copy link

maorgershman commented Dec 5, 2022

This is a workaround I use:

const areFloatsEqual = (a: number, b: number, percision: number) => {
  return Math.round(a * Math.pow(10, percision)) === Math.round(b * Math.pow(10, percision));
}

const Screen = (props: { 
  children: ReactNode,

  // https://github.com/software-mansion/react-native-screens/issues/1504
  workaroundNativeStackNavigatorHeaderBug?: boolean,
}) => {
  const { children, workaroundNativeStackNavigatorHeaderBug } = props;
  
  const [correctHeight, setCorrectHeight] = useState<number>();
  const [currentHeight, setCurrentHeight] = useState<number>();
  const wasCorrectHeightAchieved = useRef(false);

  const { height: windowHeight } = Dimensions.get('window');
  const headerHeight = useHeaderHeight();
  useEffect(() => {
    if (!workaroundNativeStackNavigatorHeaderBug) {
      return;
    }

    setCorrectHeight(windowHeight - headerHeight);
  }, []);

  const shouldRender = (() => {
    if (!workaroundNativeStackNavigatorHeaderBug) {
      return true;
    }

    if (wasCorrectHeightAchieved.current) {
      return true;
    }

    if (typeof correctHeight === 'undefined' || typeof currentHeight === 'undefined') {
      return false;
    }

    const percision = 2;
    const result = areFloatsEqual(correctHeight, currentHeight, percision);
    wasCorrectHeightAchieved.current = result;
    return result;
  })();

  return (
    <View 
      style={{ flex: 1 }}
      onLayout={(e) => {
        if (!workaroundNativeStackNavigatorHeaderBug) {
          return;
        }

        if (wasCorrectHeightAchieved.current) {
          return;
        }
        
        setCurrentHeight(e.nativeEvent.layout.height);
      }}
    >
      {
        shouldRender && 
        children 
      }
    </View>
  );
}
<NativeStack.Navigator>
  <NativeStack.Screen
    name='MyScreen'
    component={() => {
      return (
        <Screen workaroundNativeStackNavigatorHeaderBug>
          { /* You stuff goes here... */ }
        </Screen>
      );
    }}
  />
</NativeStack.Navigator>

@lyqandy
Copy link

lyqandy commented Apr 18, 2023

same issue

@dylancom
Copy link
Contributor

dylancom commented Jun 6, 2023

Having the same issue. I found that it is caused by #222 & #184, which updates the size after it was rendered once.

// when nav bar is not translucent we chage edgesForExtendedLayout to avoid system laying out
// the screen underneath navigation controllers
vc.edgesForExtendedLayout = UIRectEdgeNone;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Missing info The user didn't precise the problem enough Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided
Projects
None yet
Development

No branches or pull requests

7 participants