Skip to content

[iOS] Fix force RTL support on new architecture. #49455

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

Closed
wants to merge 1 commit into from

Conversation

chrsmys
Copy link
Contributor

@chrsmys chrsmys commented Feb 16, 2025

Summary

This fixes an issue in Fabric where changing the layout direction and then reloading the JS bundle did not honor the layout direction until the app was restarted on iOS. This now calls _updateLayoutContext whenever RCTSurfaceView is recreated which happens on bundle reload. This is not an issue on the old architecture because the layout direction is determined within the SurfaceViews which were recreated on bundle reload.

Related Issues:

How can we take this further?

If we want to make it so that it doesn't require an entire bundle reload for RTL to take effect I believe these are the steps that would need to be taken:

  • Make it so RCTI18nManager exports isRTL as a method instead of consts
  • Send Notification Center notif when RTL is forced on or off
  • Listen for that notification RCTSurfaceView and call _updateLayoutContext similar to UIContentSizeCategoryDidChangeNotification.

Changelog:

[iOS] [FIXED] - Layout direction changes are now honored on bundle reload.

Test Plan:

On the new architecture change force the layout direction and reload the bundle:

import React, { useCallback } from "react";
import { Button, I18nManager, StyleSheet, Text, View } from "react-native";
import RNRestart from "react-native-restart";

export default function Explore() {
    const onApplyRTL = useCallback(() => {
        if (!I18nManager.isRTL) {
            I18nManager.forceRTL(true);
            RNRestart.restart();
        }
    }, []);

    const onApplyLTR = useCallback(() => {
        if (I18nManager.isRTL) {
            I18nManager.forceRTL(false);
            RNRestart.restart();
        }
    }, []);

    return (
        <View style={styles.area}>
            <Text>Test Block</Text>
            <View style={styles.testBlock}>
                <Text>Leading</Text>
                <Text>Trailing</Text>
            </View>
            <Button title={"Apply RTL"} onPress={onApplyRTL} />
            <Button title={"Apply LTR"} onPress={onApplyLTR} />
        </View>
    );
}

const styles = StyleSheet.create({
    area: {
        marginVertical: 50,
        paddingHorizontal: 24,
    },
    testBlock: {
        paddingVertical: 10,
        flexDirection: "row",
        justifyContent: "space-between",
    },
});

Simulator.Screen.Recording.-.iPhone.16.Plus.-.2025-02-16.at.14.05.30.mp4

@facebook-github-bot facebook-github-bot added CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Feb 16, 2025
@cipolleschi
Copy link
Contributor

@NickGerleman can you have a look at this? From our internal discussions, I kind of understood that the fix should rely in Yoga/ShadowNode and not in the components' superclass, but you have more experience than me in this area.

@NickGerleman
Copy link
Contributor

This seems perfectly sane to me. I18nManager change should effect root constraint like this, which influences call to YGNodeCalculateLayout, instead of anything in the Yoga tree explicitly.

The followups to support this without reload also make sense! Fabric Android, last I tested, may actually do this already without bundle reload, but I'm not sure this behavior was explicit as much as emergent.

@facebook-github-bot
Copy link
Contributor

@NickGerleman has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@a-eid
Copy link

a-eid commented Feb 19, 2025

@chrsmys Thank you for this contribution, I will patch my local RN & report back the results. also making RTL LTR direction changes without needing a bundle reload would be perfect. however it'll also require work for android.

@facebook-github-bot facebook-github-bot added the Merged This PR has been merged. label Feb 19, 2025
@facebook-github-bot
Copy link
Contributor

@NickGerleman merged this pull request in 36f29be.

@react-native-bot
Copy link
Collaborator

This pull request was successfully merged by @chrsmys in 36f29be

When will my fix make it into a release? | How to file a pick request?

@PranavAvasthi
Copy link

PranavAvasthi commented Mar 3, 2025

@cipolleschi @chrsmys When will this be released?

@SalmanFaris53
Copy link

For me still its not working...drawermenu is not changing , bottom tab is not changing like from ltr to rtl

@PranavAvasthi
Copy link

@a-eid Have you tried this? If Yes, can you update that is it working or not?

@cipolleschi
Copy link
Contributor

We cut the branch for 0.79 yesterday. 0.79 will be out the week of the 7th of April, if nothing major happen.

If you want the fix to be backported, please open a pick request. Please open a different pick request for each version you'd like for the fix to be backported.

@ahmamedhat
Copy link

I tried this and its not working with bottom tabs

@a-eid
Copy link

a-eid commented Mar 8, 2025

@PranavAvasthi @ahmamedhat I have tried the fix and it seem to be working as expected.

@ahmamedhat
Copy link

@a-eid Please if you can tell me how did you try it? maybe I'm missing something

@a-eid
Copy link

a-eid commented Mar 8, 2025

@ahmamedhat I added the following patch, recompiled my project and it just worked.

diff --git a/React/Fabric/Surface/RCTFabricSurface.mm b/React/Fabric/Surface/RCTFabricSurface.mm
index 7b3ce5918ce5a159ef364cec030dd1dd00d587bb..3906df778bcd03038c9fd49fb52346cc252f2489 100644
--- a/React/Fabric/Surface/RCTFabricSurface.mm
+++ b/React/Fabric/Surface/RCTFabricSurface.mm
@@ -143,6 +143,7 @@ - (RCTSurfaceView *)view
 
   if (!_view) {
     _view = [[RCTSurfaceView alloc] initWithSurface:(RCTSurface *)self];
+    [self _updateLayoutContext];
     _touchHandler = [RCTSurfaceTouchHandler new];
     [_touchHandler attachToView:_view];
   }

react-native-bot pushed a commit that referenced this pull request Mar 10, 2025
Summary:
This fixes an issue in Fabric where changing the layout direction and then reloading the JS bundle did not honor the layout direction until the app was restarted on iOS. This now calls  `_updateLayoutContext` whenever RCTSurfaceView is recreated which happens on bundle reload. This is not an issue on the old architecture because the layout direction is determined within the [SurfaceViews](https://github.com/facebook/react-native/blob/acdddef48eb60b002c954d7d2447cb9c2883c8b3/packages/react-native/React/Views/RCTRootShadowView.m#L18) which were recreated on bundle reload.

## Related Issues:
- react-native-community/discussions-and-proposals#847
- #49451
- #48311
- #45661

## How can we take this further?
If we want to make it so that it doesn't require an entire bundle reload for RTL to take effect I believe these are the steps that would need to be taken:
- Make it so [RCTI18nManager](https://github.com/facebook/react-native/blob/acdddef48eb60b002c954d7d2447cb9c2883c8b3/packages/react-native/React/CoreModules/RCTI18nManager.mm#L52) exports isRTL as a method instead of consts
- Send Notification Center notif when RTL is forced on or off
- Listen for that notification RCTSurfaceView and call _updateLayoutContext similar to UIContentSizeCategoryDidChangeNotification.

## Changelog:

[iOS] [FIXED] - Layout direction changes are now honored on bundle reload.

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests

Pull Request resolved: #49455

Test Plan:
On the new architecture change force the layout direction and reload the bundle:
```
import React, { useCallback } from "react";
import { Button, I18nManager, StyleSheet, Text, View } from "react-native";
import RNRestart from "react-native-restart";

export default function Explore() {
    const onApplyRTL = useCallback(() => {
        if (!I18nManager.isRTL) {
            I18nManager.forceRTL(true);
            RNRestart.restart();
        }
    }, []);

    const onApplyLTR = useCallback(() => {
        if (I18nManager.isRTL) {
            I18nManager.forceRTL(false);
            RNRestart.restart();
        }
    }, []);

    return (
        <View style={styles.area}>
            <Text>Test Block</Text>
            <View style={styles.testBlock}>
                <Text>Leading</Text>
                <Text>Trailing</Text>
            </View>
            <Button title={"Apply RTL"} onPress={onApplyRTL} />
            <Button title={"Apply LTR"} onPress={onApplyLTR} />
        </View>
    );
}

const styles = StyleSheet.create({
    area: {
        marginVertical: 50,
        paddingHorizontal: 24,
    },
    testBlock: {
        paddingVertical: 10,
        flexDirection: "row",
        justifyContent: "space-between",
    },
});

```

https://github.com/user-attachments/assets/0eab0d79-de3f-4eeb-abd0-439ba4fe25c0

Reviewed By: cortinico, cipolleschi

Differential Revision: D69797645

Pulled By: NickGerleman

fbshipit-source-id: 97499621f3dd735d466f5119e0f2a0eccf1c3c05
@react-native-bot
Copy link
Collaborator

This pull request was successfully merged by @chrsmys in 23b888f

When will my fix make it into a release? | How to file a pick request?

@1880akshay
Copy link

@cortinico when will the release (0.76.8 probably) happen with this fix?

@a-eid
Copy link

a-eid commented Mar 21, 2025

@chrsmys I am facing this weird issues sometimes where when I forceRTL to either true or false, I18nManager.isRTL doesn't update properly... however the layout does... any idea what might cause this ?

@chrsmys
Copy link
Contributor Author

chrsmys commented Mar 21, 2025

@chrsmys I am facing this weird issues sometimes where when I forceRTL to either true or false, I18nManager.isRTL doesn't update properly... however the layout does... any idea what might cause this ?

Are you reloading the bundle afterwards? At the moment that is a requirement. This weekend I may look into the additional fixes to make this unnecessary (Referenced in "How can we take this further?" section).

@a-eid
Copy link

a-eid commented Mar 21, 2025

@chrsmys Yes. Keep in mind that everything works fine after a reload. Elements that are laid out from left to right correctly switch to right to left. However, I18nManager.isRTL does not update, which causes issues with elements like arrows and the iOS navigation transition direction.

@chrsmys
Copy link
Contributor Author

chrsmys commented Mar 21, 2025

Can you file a new issue with a small repro project? I can take a look this weekend.

@SalmanFaris53
Copy link

SalmanFaris53 commented Mar 22, 2025 via email

@a-eid
Copy link

a-eid commented Mar 22, 2025

@SalmanFaris53 that's because arrows & drawers direction is depending on I18nManager.isRTL boolean flag, which doesn't update some times.

@a-eid
Copy link

a-eid commented Mar 26, 2025

@chrsmys I've created a repro, I am working on my STR, cuz it doesn't always happen.

cc @SalmanFaris53 let me know if you're able to help.

@KarinaOliinykCommandpost

I also have this issue on iOS

@baraa-rasheed
Copy link

@a-eid The UI is being updated correctly but the native-stack navigator is not, you can test this by setting the headerLargeTitle to true and try to change the RTL direction.

@a-eid
Copy link

a-eid commented Apr 10, 2025

@baraa-rasheed everything is updating for me just fine, however sometimes I18nManager.isRTL doesn't update correct. which affects the navigation direction. not sure why. hopefully @chrsmys will have an update soon.

@cipolleschi
Copy link
Contributor

Stack navigator is part of react-avigation, is it perhaps worth opening an issue in the react-navigation repository.

@fawwazAlkarmy
Copy link

I have still an issues with RTL on IOS , i'm using
"expo": "^52.0.44",
"react-native": "0.76.9",

on first run the layout isn't correct , after i reload it works fine the layout changes , however not all elements works correctly, for example the same Text component works correctly in some places and doesn't in others, i don't know why i have this weird issue till now, any help will be appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.