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 back button/menu title always is "back" when headerShown is false #1864

Closed
h-five-e opened this issue Aug 9, 2023 · 9 comments · Fixed by #1866
Closed

iOS back button/menu title always is "back" when headerShown is false #1864

h-five-e opened this issue Aug 9, 2023 · 9 comments · Fixed by #1866
Assignees
Labels
Bug Something isn't working Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided

Comments

@h-five-e
Copy link

h-five-e commented Aug 9, 2023

Description

If you have a stack of screens, the back button and the back button menu show the title of the previous screen, except when the header of the previous screen is not shown, in which case the shown title will revert to "back" (or localised equivalent).

For example:

<Stack.Navigator>
  <Stack.Screen name="screenA" component={ScreenA} options={{headerTitle: 'screen a title', headerShown: false}}/>
  <Stack.Screen name="screenB" component={ScreenB} options={{headerTitle: 'screen b title'}}/>
  <Stack.Screen name="screenC" component={ScreenC} options={{headerTitle: 'screen c title', headerShown: false}} />
  <Stack.Screen name="screenD" component={ScreenD} options={{headerTitle: 'screen d title'}} />
  <Stack.Screen name="screenE" component={ScreenE} options={{headerTitle: 'screen e title'}} />
</Stack.Navigator>

The names of screenA and screenC will be back where as the others will show screen x title. I expect this to be screen x title for all screens, regardless of whether or not the header has been shown.

image image

I have provided a snack. I am not quite familiar with Expo and Snacks and the versions differ a little from my app (for example I am using react native 0.71.7 and react-native-screens 3.24 locally, which are newer) but it has the same issue on the newer versions.

Steps to reproduce

  1. Create a navigator with a screen where the option headerShown is false and another screen to navigate to from there
  2. Navigate to the second screen, note the back button/menu

Snack or a link to a repository

https://snack.expo.dev/@hanneke/navigation-ios-back-name-not-properly-set-repro

Screens version

3.20.0

React Native version

0.71.3

Platforms

iOS

JavaScript runtime

Hermes

Workflow

React Native (without Expo)

Architecture

Paper (Old Architecture)

Build type

Debug mode

Device

Real device

Device model

iPhone 11 16.5.1

Acknowledgements

Yes

@github-actions github-actions bot added Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided labels Aug 9, 2023
@kkafar kkafar self-assigned this Aug 9, 2023
@kkafar
Copy link
Member

kkafar commented Aug 9, 2023

Thanks for reporting, I can confirm the issue, will look into it.

@kkafar
Copy link
Member

kkafar commented Aug 9, 2023

Hey @h-five-e,

would you mind trying fix from #1866?
I haven't tested it exhaustively yet, but it looks like it should work fine.

To test the fix you can install screens directly from branch in your package.json:

"react-native-screens": "software-mansion/react-native-screens#@kkafar/bad-text-on-back-button"

@h-five-e
Copy link
Author

Oh wow, that's really quick! That does indeed fix it 🙏🏻 thank you!

I do run into a semi-related issue. If, say, screen C uses a headerBackTitle that is also what gets pushed onto the menu as the name for screen B, instead of screen B's headerTitle. A cursory scan does not reveal related issues, should I create one (if there indeed isn't one) or is this expected behaviour?

@kkafar
Copy link
Member

kkafar commented Aug 10, 2023

I do run into a semi-related issue. If, say, screen C uses a headerBackTitle that is also what gets pushed onto the menu as the name for screen B, instead of screen B's headerTitle. A cursory scan does not reveal related issues, should I create one (if there indeed isn't one) or is this expected behaviour?

I do not consider it a bug. IIRC headerBackTitle defaults to header / route title. When it is set (e.g. headerBackTitle: 'foo') it overrides these values & IMO the name next to back button & name in the back button context menu should be consistent.

@h-five-e
Copy link
Author

Cool, thanks for the feedback. We use it to provide a "back" text in cases where the title is a bit long (related to #1589), so we're keeping our fingers crossed on a fix there although that seems a little more involved 😅.

Anyway this particular issue seems to be resolved, thank you ☺️.

@kkafar
Copy link
Member

kkafar commented Aug 10, 2023

It looks like #1589 was already fixed in native stack v6 @react-navigation/native-stack here:

Does this still happen there?

My follow up question would be: do you import createNativeStackNavigator from @react-navigation/native-stack or react-native-screens/native-stack?

@h-five-e
Copy link
Author

I did a quick check, I'm using "@react-navigation/native-stack": "6.9.13", from which I import createNativeStackNavigator.

I cannot check back in until Monday, if necessary I can work on a repro then/Tuesday (and open a new report).

I hope you have an enjoyable weekend!
-H

@kkafar
Copy link
Member

kkafar commented Aug 11, 2023

So according to react-navigation/react-navigation#10844 that issue should be fixed. In case it is still present - let me know.

kkafar added a commit that referenced this issue Aug 11, 2023
#1866)

## Description

Ah, here we go again...

When header is hidden (`headerShown: false` in v6, `hidden: true` in v5)
the method that updates header configuration returns early just after
setting some layout related and LTR/RTL options, thus `title` property
of current `UINavigationItem` is left unset leading to system default
name `Back` being displayed in "back context menu".

Fixes #1864

## Changes

When returning early (because header is hidden) we now set the
`UINavigationItem` property to proper value.

## Test code and steps to reproduce

`Test1864` in `FabricTestExample` & `TestsExample`.

I've also tested it in context of

* #1646

and mix of both. Seems to work fine.

## Checklist

- [x] Included code example that can be used to test this change
- [x] Ensured that CI passes
@h-five-e
Copy link
Author

It seems to be a regression in react-native-screens at least in our production app; it's fine in 3.20 but not in 3.22/3.24. I'll provide a repro later (and stop discussing it here).

maciekstosio added a commit that referenced this issue May 16, 2024
## Description

~This PR improves upon #2105. #2105 allowed to use iOS 14 default back
button behavior when label is not provided. This PR allows to modify the
behavior by allowing to provide UINavigationButtonBackButtonDisplayMode
and enables it for custom text (without style modifications). The main
problem is that we used to provide backButtonItem in most of the cases
which
[disables](https://developer.apple.com/documentation/uikit/uinavigationitem/3656350-backbuttondisplaymode)
backButtonDisplayMode.~

This PR adds possibility to customize default behavior of back button
using `backButtonDisplayMode`
([UINavigationBackButtonDisplayMode](https://developer.apple.com/documentation/uikit/uinavigationitem/backbuttondisplaymode))
for iOS.

:warning: **This modifies only default back button**, when any
customization is added (including headerBackTitle) in native part we
create custom `RNSUIBarButtonItem` and set it as `backButtonItem`, which
[disables](https://developer.apple.com/documentation/uikit/uinavigationitem/3656350-backbuttondisplaymode)
`backButtonDisplayMode` behavior.

I tried to make it work together with custom label (`headerBackTitle`)
using `prevItem.backButtonTitle`, but due to iOS limitations it is not
viable option. It influences also back button menu - changes the label
of previous screen - which is not the behavior we want.

To sum up, `backButtonDisplayMode` work when none of:
- `headerBackTitleStyle.fontFamily`
- `headerBackTitleStyle.fontSize`
- `headerBackTitle`
- `disableBackButtonMenu`

are set. 

## Screenshots / GIFs

|Paper|Fabric|
|-|-|
|<video
src="https://github.com/software-mansion/react-native-screens/assets/11800297/c6aa7697-4331-4cb4-a81d-7f77f128513d"
/>|<video
src="https://github.com/software-mansion/react-native-screens/assets/11800297/fa0edd92-1aa2-45e5-a466-516c0ec120d2"
/>|

<details>
<summary>Example component used in tests:</summary>

```jsx
import * as React from 'react';
import { Button, View, Text, StyleSheet } from 'react-native';
import { NavigationContainer, ParamListBase } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();

type NavProp = {
  navigation: NativeStackNavigationProp<ParamListBase>;
};

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="screenA"
          component={ScreenA}
          options={{ headerTitle: 'A: Home' }}
        />
        <Stack.Screen
          name="screenB"
          component={ScreenB}
          options={{
            headerTitle: 'B: default',
            backButtonDisplayMode: 'default',
          }}
        />
        <Stack.Screen
          name="screenC"
          component={ScreenC}
          options={{
            headerTitle: 'C: generic',
            backButtonDisplayMode: 'generic',
          }}
        />
        <Stack.Screen
          name="screenD"
          component={ScreenD}
          options={{
            headerTitle: 'D: minimal',
            backButtonDisplayMode: 'minimal',
          }}
        />
        <Stack.Screen
          name="screenE"
          component={ScreenE}
          options={{
            headerTitle: 'E: custom',
            headerBackTitle: 'Back Title',
            backButtonDisplayMode: 'minimal',
          }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

const ScreenA = ({ navigation }: NavProp) => (
  <View style={styles.container}>
    <Text>Screen A</Text>
    <Button
      onPress={() => navigation.navigate('screenB')}
      title="Go to screen B"
    />
  </View>
);

const ScreenB = ({ navigation }: NavProp) => (
  <View style={styles.container}>
    <Text>Screen B</Text>
    <Text>backButtonDisplayMode: default</Text>
    <Button
      onPress={() => navigation.navigate('screenC')}
      title="Go to screen C"
    />
  </View>
);

const ScreenC = ({ navigation }: NavProp) => (
  <View style={{ flex: 1, paddingTop: 50 }}>
    <Text>Screen C</Text>
    <Text>backButtonDisplayMode: generic</Text>
    <Button
      onPress={() => navigation.navigate('screenD')}
      title="Go to screen D"
    />
  </View>
);

const ScreenD = ({ navigation }: NavProp) => (
  <View style={styles.container}>
    <Text>Screen D</Text>
    <Text>backButtonDisplayMode: minimal</Text>
    <Button
      onPress={() => navigation.navigate('screenE')}
      title="Go to screen E"
    />
  </View>
);

const ScreenE = (_props: NavProp) => (
  <View style={styles.container}>
    <Text>Screen E</Text>
    <Text>backButtonDisplayMode omitted because of the headerBackTitle</Text>
  </View>
);

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: 'center', justifyContent: 'space-around' },
});
```

</details>

## Checklist

- [x] Included code example that can be used to test this change
- [x] Updated TS types
- [x] Updated documentation: <!-- For adding new props to native-stack
-->
- [x]
https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md
- [x]
https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md
- [x]
https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx
- [x]
https://github.com/software-mansion/react-native-screens/blob/main/src/native-stack/types.tsx
- [x] Ensured that CI passes

Tested #1864: Paper ✅ Fabric ✅
Tested #1646: Paper ❌ Fabric ❌ - but it does not work on main too, could
now be achieved using `backButtonDisplayMode: ‘minimal’`

---------

Co-authored-by: Kacper Kafara <kacper.kafara@swmansion.com>
ja1ns pushed a commit to WiseOwlTech/react-native-screens that referenced this issue Oct 9, 2024
software-mansion#1866)

## Description

Ah, here we go again...

When header is hidden (`headerShown: false` in v6, `hidden: true` in v5)
the method that updates header configuration returns early just after
setting some layout related and LTR/RTL options, thus `title` property
of current `UINavigationItem` is left unset leading to system default
name `Back` being displayed in "back context menu".

Fixes software-mansion#1864

## Changes

When returning early (because header is hidden) we now set the
`UINavigationItem` property to proper value.

## Test code and steps to reproduce

`Test1864` in `FabricTestExample` & `TestsExample`.

I've also tested it in context of

* software-mansion#1646

and mix of both. Seems to work fine.

## Checklist

- [x] Included code example that can be used to test this change
- [x] Ensured that CI passes
ja1ns pushed a commit to WiseOwlTech/react-native-screens that referenced this issue Oct 9, 2024
…e-mansion#2123)

## Description

~This PR improves upon software-mansion#2105. software-mansion#2105 allowed to use iOS 14 default back
button behavior when label is not provided. This PR allows to modify the
behavior by allowing to provide UINavigationButtonBackButtonDisplayMode
and enables it for custom text (without style modifications). The main
problem is that we used to provide backButtonItem in most of the cases
which
[disables](https://developer.apple.com/documentation/uikit/uinavigationitem/3656350-backbuttondisplaymode)
backButtonDisplayMode.~

This PR adds possibility to customize default behavior of back button
using `backButtonDisplayMode`
([UINavigationBackButtonDisplayMode](https://developer.apple.com/documentation/uikit/uinavigationitem/backbuttondisplaymode))
for iOS.

:warning: **This modifies only default back button**, when any
customization is added (including headerBackTitle) in native part we
create custom `RNSUIBarButtonItem` and set it as `backButtonItem`, which
[disables](https://developer.apple.com/documentation/uikit/uinavigationitem/3656350-backbuttondisplaymode)
`backButtonDisplayMode` behavior.

I tried to make it work together with custom label (`headerBackTitle`)
using `prevItem.backButtonTitle`, but due to iOS limitations it is not
viable option. It influences also back button menu - changes the label
of previous screen - which is not the behavior we want.

To sum up, `backButtonDisplayMode` work when none of:
- `headerBackTitleStyle.fontFamily`
- `headerBackTitleStyle.fontSize`
- `headerBackTitle`
- `disableBackButtonMenu`

are set. 

## Screenshots / GIFs

|Paper|Fabric|
|-|-|
|<video
src="https://github.com/software-mansion/react-native-screens/assets/11800297/c6aa7697-4331-4cb4-a81d-7f77f128513d"
/>|<video
src="https://github.com/software-mansion/react-native-screens/assets/11800297/fa0edd92-1aa2-45e5-a466-516c0ec120d2"
/>|

<details>
<summary>Example component used in tests:</summary>

```jsx
import * as React from 'react';
import { Button, View, Text, StyleSheet } from 'react-native';
import { NavigationContainer, ParamListBase } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();

type NavProp = {
  navigation: NativeStackNavigationProp<ParamListBase>;
};

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="screenA"
          component={ScreenA}
          options={{ headerTitle: 'A: Home' }}
        />
        <Stack.Screen
          name="screenB"
          component={ScreenB}
          options={{
            headerTitle: 'B: default',
            backButtonDisplayMode: 'default',
          }}
        />
        <Stack.Screen
          name="screenC"
          component={ScreenC}
          options={{
            headerTitle: 'C: generic',
            backButtonDisplayMode: 'generic',
          }}
        />
        <Stack.Screen
          name="screenD"
          component={ScreenD}
          options={{
            headerTitle: 'D: minimal',
            backButtonDisplayMode: 'minimal',
          }}
        />
        <Stack.Screen
          name="screenE"
          component={ScreenE}
          options={{
            headerTitle: 'E: custom',
            headerBackTitle: 'Back Title',
            backButtonDisplayMode: 'minimal',
          }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

const ScreenA = ({ navigation }: NavProp) => (
  <View style={styles.container}>
    <Text>Screen A</Text>
    <Button
      onPress={() => navigation.navigate('screenB')}
      title="Go to screen B"
    />
  </View>
);

const ScreenB = ({ navigation }: NavProp) => (
  <View style={styles.container}>
    <Text>Screen B</Text>
    <Text>backButtonDisplayMode: default</Text>
    <Button
      onPress={() => navigation.navigate('screenC')}
      title="Go to screen C"
    />
  </View>
);

const ScreenC = ({ navigation }: NavProp) => (
  <View style={{ flex: 1, paddingTop: 50 }}>
    <Text>Screen C</Text>
    <Text>backButtonDisplayMode: generic</Text>
    <Button
      onPress={() => navigation.navigate('screenD')}
      title="Go to screen D"
    />
  </View>
);

const ScreenD = ({ navigation }: NavProp) => (
  <View style={styles.container}>
    <Text>Screen D</Text>
    <Text>backButtonDisplayMode: minimal</Text>
    <Button
      onPress={() => navigation.navigate('screenE')}
      title="Go to screen E"
    />
  </View>
);

const ScreenE = (_props: NavProp) => (
  <View style={styles.container}>
    <Text>Screen E</Text>
    <Text>backButtonDisplayMode omitted because of the headerBackTitle</Text>
  </View>
);

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: 'center', justifyContent: 'space-around' },
});
```

</details>

## Checklist

- [x] Included code example that can be used to test this change
- [x] Updated TS types
- [x] Updated documentation: <!-- For adding new props to native-stack
-->
- [x]
https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md
- [x]
https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md
- [x]
https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx
- [x]
https://github.com/software-mansion/react-native-screens/blob/main/src/native-stack/types.tsx
- [x] Ensured that CI passes

Tested software-mansion#1864: Paper ✅ Fabric ✅
Tested software-mansion#1646: Paper ❌ Fabric ❌ - but it does not work on main too, could
now be achieved using `backButtonDisplayMode: ‘minimal’`

---------

Co-authored-by: Kacper Kafara <kacper.kafara@swmansion.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Something isn't working Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided
Projects
None yet
2 participants