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

Header jump when animation is set for bottom tab navigator

Open matinzd opened this issue 1 year ago • 23 comments

Description

Moving this issue from https://github.com/react-navigation/react-navigation/issues/12273.

I can't repro this on JS stack, and the headerTopInsetEnabled prop for screens is always set to true. So it seems to be an issue with react-native-screens.

The header will have an initial jump when animation is set for tab navigator. It doesn't matter if it's set to fade or shift.

https://github.com/user-attachments/assets/8435fe72-3fad-4e48-a27e-ce92c11d1ce9

Expected behavior

The header should not flicker or have jumps.

https://github.com/user-attachments/assets/b13aeb15-2d52-4955-aeba-87ae63f6ac84

Steps to reproduce

Set bottom tab animation to fade or shift.

Snack or a link to a repository

https://github.com/matinzd/ReactNavigationHeaderFlicker

Screens version

4.1.0

React Native version

0.76.2

Platforms

iOS

JavaScript runtime

Hermes

Workflow

React Native (without Expo)

Architecture

Fabric (New Architecture)

Build type

Debug mode

Device

iOS simulator

Device model

No response

Acknowledgements

Yes

matinzd avatar Nov 22 '24 11:11 matinzd

@matinzd ~After almost 4 hours on this bug, this hack fixed the issue for me: https://github.com/software-mansion/react-native-screens/issues/1779#issuecomment-1580291463~

This doesn't fix the issue.

za01br avatar Nov 27 '24 07:11 za01br

That is the same issue but it's not caused by the same thing. Now the new tab animation is causing this issue which is not the same as that one.

The workaround may work but it's not a correct solution. I also usually use scroll view with automatic inset adjustment so that I don't need to take care of the header height.

matinzd avatar Nov 27 '24 09:11 matinzd

I agree with you. And that hack actually doesn't fix the issue.

za01br avatar Nov 28 '24 22:11 za01br

Alright, I could pretty much narrow it down. It is indeed related to the new animation I guess, and in my case it was triggered because we enabled fullScreenGestureEnabled, which under the hood switches to simple_push.

It's now also reproducible in the Expo Snack using stock react navigation v7. https://snack.expo.dev/@hirbod/native-stack-navigator-header-to-non-header

I suspect this PR caused it: https://github.com/software-mansion/react-native-screens/pull/2477 @kkafar

https://github.com/user-attachments/assets/2f2fbc2e-cf48-438f-b3a9-ebaf94e0060d

EDIT: 100% isolated reproducible with react-native-screens alone. Here is the issue for it: https://github.com/software-mansion/react-native-screens/issues/2550

hirbod avatar Dec 05 '24 02:12 hirbod

Any attempts to fix this?

matinzd avatar Jan 03 '25 07:01 matinzd

I also found out that useHeaderHeight changes to some weird values when screen is mounted within tab navigation.

Here is how useHeaderHeight behaves when component re-renders when it's inside tab navigator:

 LOG  headerHeight 100.66666666666666
 LOG  headerHeight 44
 LOG  headerHeight 100.33333333333334

Here is how it behvaes when we navigate to another stack:

 LOG  headerHeight 100.66666666666666
 LOG  headerHeight 100.33333333333334

You can see it here as well:

https://github.com/matinzd/ReactNavigationHeaderFlicker/commit/31a433848f2d5a587fb4f42c4795fb199f60921d

matinzd avatar Jan 03 '25 07:01 matinzd

UPDATE

useHeaderHeight still behaves the same even if animation is not set to fade in tab navigator. It starts with a correct values then it jumps back to much lower value then back to the same value causing jumps if someone is using this hook.


I updated the reproducible. Please take a look at it. This is so annoying.

matinzd avatar Jan 03 '25 07:01 matinzd

I digged more into the react navigation code and I found that those incorrect values are being emitted from onHeaderHeightChange which comes from ScreenStackItem:

https://github.com/react-navigation/react-navigation/blob/main/packages/native-stack/src/views/NativeStackView.native.tsx#L343C11-L343C31

matinzd avatar Jan 03 '25 08:01 matinzd

I tried to debug it a bit with xcode.

This is what happens in the first calculation for the header height:

Screenshot 2025-01-03 at 09 39 04

Then the value is now corrected in the second run:

Screenshot 2025-01-03 at 09 39 08

navbarInset is in the first run.

matinzd avatar Jan 03 '25 08:01 matinzd

Sorry for mentioning you here but It's been a long time I opened this issue and I am still waiting for your response. I know you are very busy with all the new arch changes but it would be so helpful if we can also get this issue fixed. I am also available to hop on a call with you on discord to dig into this. Thank you @kkafar for all the work you are putting in for this repo!

matinzd avatar Jan 07 '25 12:01 matinzd

I haven't had opportunity to work on this yet, but I should have capacity to look into it tomorrow / at Monday.

kkafar avatar Jan 16 '25 16:01 kkafar

Any updates @kkafar ? This issue is so annoying and causes a unnecessary re-renders inside the pages we use as well as visual jumps.

matinzd avatar Feb 06 '25 08:02 matinzd

Sorry, still haven't looked into it yet. Will do it soon

kkafar avatar Feb 06 '25 09:02 kkafar

Hey @matinzd, ~this problem is not reproducible using the provided example.~ (see below 👇🏻)

https://github.com/user-attachments/assets/082cb67f-9c31-44ae-80e1-097bd15d3733

~Do I need to do some extra steps, or I can assume this works now?~

kkafar avatar Feb 06 '25 13:02 kkafar

Oh, I see the commented line now. I confirm the issue is reproducible

kkafar avatar Feb 06 '25 13:02 kkafar

Important note: if you swap scrollview for regular view it works correctly.

kkafar avatar Feb 06 '25 15:02 kkafar

I have a grasp on what's going on here. Making very long story short:

  1. When animation is not set on tab navigator, the "two state container" is being used - it uses RNSNavigationController & RNSScreenNavigationContainerView under the hood for managing transitions - works as expected.
  2. In opposite case - plain RNSScreenContainerView & RNSViewController are being used leading to situation where the UIKit callbacks _UIScrollViewAdjustForOverlayInsetsChangeIfNecessary are coming after the transaction completes, and not before it is scheduled - leading to corrupted UI state.

Leaving this as a note for myself to continue investigation tomorrow on how to fix this.

@matinzd Do you happen to have context on when was this bug introduced? Is this regression os has it always happened? I doubt #2477 is responsible because the RNSScreenStackAnimator modified there is literally not created even once.

kkafar avatar Feb 06 '25 17:02 kkafar

I confirmed this is also an issue on RN 0.76 & react-native-screens 3.35.0. Therefore I recon that this is a long standing issue not introduced by v4.

For future me:

I've spent some more time today debugging and do not have any big breakthrough. Only thing I learned is that the _UIScrollViewAdjustForOverlayInsetsChangeIfNecessary is called on time in both cases, but in case where there is no UINavigationController present the computed offset value is 0.f until the transition finishes -> therefore this method fails to set the correct value. I've determined this by looking through assembler of _UIScrollViewAdjustForOverlayInsetsChangeIfNecessary, however I could not find out why it is the case as the current offset value is passed as argument to this method (through d0 register), but I've failed to navigate up the stack frames of private UIKit methods with lldb to determine how it is computed. 🤷‍♂

My working hypothesis is that it works fine in presence of navigation controller, because setViewControllers attaches the views to the model layer, allowing correct layout to execute and the transition starts in yet another iteration of main run loop. While attaching the views via regular view controller causes the transition to start first. No idea how to fix this sensibly yet.

kkafar avatar Feb 07 '25 18:02 kkafar

Thanks for the investigation @kkafar ! Let me know when this is fixed.

matinzd avatar Feb 10 '25 09:02 matinzd

I can experience this issue with RN 0.76.9 and react-native-screens 4.4.0. Does anyone know if this is reproducible on RN 0.77+?

lukhol avatar Apr 10 '25 20:04 lukhol

Most likely it is. I haven't had opportunity to return to the issue yet. I'll try to look into it next week.

kkafar avatar Apr 11 '25 07:04 kkafar

Hi @kkafar Is there any update for this ? I am still experiencing this issue. Using "react-native": "0.78.0"

singhagam1 avatar Sep 09 '25 07:09 singhagam1

We are experiencing a similar header jump. We've also tracked down that onHeaderHeightChange is sometimes returning 44 and then immediately after the correct number.

sidorchukandrew avatar Sep 29 '25 19:09 sidorchukandrew