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

iOS Layout Issues in New Architecture: Unexpected view height changes on re-render

Open Jpoliachik opened this issue 8 months ago • 19 comments

Description

We've seen several layout issues after switching to New Architecture as documented and reproduced here https://github.com/Jpoliachik/NewArchNavLayoutIssues I spent several days digging into these to try and find the root cause, here is what I found for iOS: (I did a similar dive for Android here: https://github.com/software-mansion/react-native-screens/issues/2803)

Isolated Issue

I isolated one specific spot where I could reproduce an unexpected layout event. After presenting the green modal, the first render shows the container view at correct height. But on ANY next re-render, the view jumps to the incorrect height. The minimal example here only updates the count inside Text, with state isolated inside its own component. No other components get re-rendered, or styles updated from JS.

I verified this by logging inside the render method of react-native-screens Screen.tsx, to make sure this component was not re-rendered from JS side. We see no renders, but we get the onLayout callback to show our new incorrect height. This repro exists on minimal-ios-repro branch here

Note: Views using React Native's Animated components (like Button or TouchableOpacity) caused immediate double-rendering on load, so we had to remove those in our repro otherwise the view would immediately jump to the incorrect height. Interesting behavior in itself, worth noting.

Image

So where is this new incorrect screen height coming from?

It's hard to know exactly. All we can see in the stack trace when the 'bad layout' happens is that a new Fabric render cycle gets scheduled. So it's safe to say that the invalid height already existed in the Fabric ShadowTree state when the next render cycle occurs to update the Text - so the ShadowTree is definitely out of sync with our view.

Potential updateBounds issue

When debugging this issue, I saw that updateBounds was getting called a lot when presenting a new screen (I count 9 calls when presenting green modal route), and the values bounce around a bit before settling. We also see our (future) incorrect height value getting set at some point earlier.

logs

Are all these updateBounds calls necessary? As an experiment I commented out the call to updateBounds inside viewDidLayoutSubviews, and while the view height is rendered a bit too tall, it does resolve the issue. No more unexpected layouts! No more jumpiness! And I no longer saw any of the related layout issues on iOS. Strange!

Workaround

So a workaround I found is to wrap all modal views with a view that manually sets the height (which is known) and comment out updateBounds natively. https://github.com/Jpoliachik/NewArchNavLayoutIssues/pull/1 This seems to work for us, I'd be very curious to hear how it works for others. It may have unwanted side effects, and is clearly a hacky fix, but I wanted to surface for others to try.

Root Issue

This still doesn't answer the root question of why this is causing issues - and why this might cause other issues throughout the app. A few examples out of the many outstanding issues:

  • https://github.com/software-mansion/react-native-screens/issues/2578
  • https://github.com/software-mansion/react-native-screens/issues/2607
  • https://github.com/software-mansion/react-native-screens/issues/2663
  • https://github.com/software-mansion/react-native-screens/issues/1981

We see in updateBounds updateState is called directly on the Shadow Node, then setNeedsLayout called on the parent controller directly afterwards.

**auto** newState = react::RNSScreenState{RCTSizeFromCGSize(**self**.bounds.size), RCTPointFromCGPoint(CGPointMake(0, headerHeight))};
_state->updateState(std::move(newState));
UINavigationController *navctr = _controller.navigationController;
[navctr.view setNeedsLayout];

Is this potentially problematic in New Architecture? I'm guessing this must be what causes the Shadow Tree state to go out of sync with native views - if this gets called in rapid succession, React must not handle all updates as expected.

  1. Is there a better way to updateBounds so we still adjust to the height of the view controller when needed, but we minimize our calls to update layout state so it only updates when necessary, and doesn't cause any layout loops or jumpiness?
  2. Is it okay to call updateState directly on a Shadow Node outside of a scheduled render cycle in Fabric at all? Or should layout updates be handled in JS so we can let React diff and apply view updates. This should be possible to do synchronously now in New Architecture Fabric renderer, so I'm wondering if that might be the safer option.

Thanks for reading, I apologize for the wall of text but I wanted to outline all the info I collected while debugging and potentially spark a larger discussion on how to best move forward with New Architecture overall.

Steps to reproduce

  1. Present a modal with full-height content
  2. Observe the height is incorrect, and changes often

See https://github.com/Jpoliachik/NewArchNavLayoutIssues

Snack or a link to a repository

https://github.com/Jpoliachik/NewArchNavLayoutIssues/tree/minimal-ios-repro

Screens version

4.9.0

React Native version

0.76.7

Platforms

iOS

JavaScript runtime

None

Workflow

None

Architecture

Fabric (New Architecture)

Build type

None

Device

None

Device model

No response

Acknowledgements

Yes

Jpoliachik avatar Mar 24 '25 19:03 Jpoliachik

Having the same issue on modal presentation on iOS.

faljabi avatar Apr 15 '25 22:04 faljabi

I am having this exact same issue in my project

benjeske avatar Apr 18 '25 21:04 benjeske

Same issue, android as well.

Maxoos avatar Apr 24 '25 00:04 Maxoos

Same, having the issue on both iOS and Android

brentlecomte-itp avatar Apr 29 '25 08:04 brentlecomte-itp

Same issue

bhavinatharva avatar May 01 '25 10:05 bhavinatharva

I tried this   ENV['RCT_NEW_ARCH_ENABLED'] = '0' and its stop flickering and working

bhavinatharva avatar May 01 '25 11:05 bhavinatharva

Firstly, thanks @Jpoliachik for the detailed explanation and breadcrumbs to follow. I've been running into this issue randomly when enabling the new architecture too. I say randomly, because it does not happen every time, even under the same circumstances.

However, from what I can see whilst debugging the native code, something is telling the modal screen that it's bigger than it actually is. I essentially see the modal screen report the following heights on an iPhone 16:

- 852 (full device height)
- 783 (correct modal height)
- 852 (jump back to full device height)

This doesn't happen on all my modals, but it does on the one which happens to have a react-native-reanimated view at the bottom of it. I don't know whether this is related, or just a coincidence.

For the time being, I've followed the suggestion from @Jpoliachik and added a fixed height to my modal screens, which appears to have resolved the issue. It's worth noting that I haven't needed to remove the updateBounds call.

I'm using:

react-native: 0.78.2
react-native-screens: 4.10.0

It feels this is possible the same issue as https://github.com/software-mansion/react-native-screens/issues/2587

chrisbianca avatar May 08 '25 11:05 chrisbianca

same issue

klawijuice avatar May 08 '25 23:05 klawijuice

any update on this? I am facing the same issue

priyanshrastogi avatar May 12 '25 11:05 priyanshrastogi

Following!

alextbogdanov avatar Jun 19 '25 05:06 alextbogdanov

I think I am witnessing similar issues in our app after enabling the new architecture.

There are two instances (one on Android and one on iOS) where on initial mount, the screen content is rendered in a view with an incorrect size. As a result, the content underlaps the bottom of the screen. After dismissing the screen and showing it again, the layout "automagically" figures out the correct screen size and everything renders out properly.

Delaying the render of the screen until the presentation animation is finished doesn't fix this issue and I haven't found any workarounds yet.

bitcrumb avatar Jun 20 '25 09:06 bitcrumb

Just tried rebuilding our app on the commit before upgrading to the new architecture and both bugs are non-existent there.

This seems like a serious issue (and not limited to iOS as the title suggest). There is something wrong with screen layout size/bounds calculation on first mount.

bitcrumb avatar Jun 20 '25 09:06 bitcrumb

I couldn't find a fix for our app. We have sign-up screens that have a bottom button bar and the flex layout to support that made all of those screens flicker when new architecture was enabled. Disabled it and all of those issues went away.

Interested if someone finds a fix.

hankwallace avatar Jun 30 '25 05:06 hankwallace

Is this something that is actively on the radar? How can we assist?

This is effectively blocking us from upgrading to the new react-native architecture. I am also not sure if the issue is with react-native-screens or if this is something inherent to the new architecture?

The issue gets worse for us, since more and more libraries require the new architecture (e.g. we can't upgrade to a new react-native-mmkv version which has an encryption bug due to not being able to adapt the new architecture because of this bug).

bitcrumb avatar Jun 30 '25 07:06 bitcrumb

Same exact issue, root component in screen has flex: 1 and onLayout gets called twice, the first time with an invalid height

ice-cap0 avatar Jul 05 '25 18:07 ice-cap0

I have the feeling this issue isn't getting the attention it needs?

bitcrumb avatar Aug 14 '25 20:08 bitcrumb

I've been struggling for quite long time, but haven't found the fix yet! It's super critical. I can reproduce it each time - when i jump between screen and the layout different

Yurii-Lutsyk avatar Aug 19 '25 12:08 Yurii-Lutsyk

Image

What can cause this issue??? |'ve commented out [self.screenView updateBounds]; but it didn't help

That's not a list of items, all of them rendered separately so there is no key collapse

The fact that that issue happens only when i'm adding a new item after return back to the screen(bottomTabs). If i'm deleting items it works properly

Yurii-Lutsyk avatar Aug 19 '25 13:08 Yurii-Lutsyk

@bitcrumb @Jpoliachik @chrisbianca Do you have any ideas?

Yurii-Lutsyk avatar Aug 19 '25 13:08 Yurii-Lutsyk