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

Header back button does not shorten to 'Back' on iOS if there's not enough space #1589

Closed
DrOverbuild opened this issue Sep 13, 2022 · 5 comments · Fixed by #2105
Closed
Labels
Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided

Comments

@DrOverbuild
Copy link

Description

According to the documentation, the back button title is supposed to change to "Back" if there's not enough space for the full title of the previous screen. That is not what I'm experiencing. In the GIF below you can see that instead of changing to "Back" it will truncate the header title, and if it's long enough, the back button will use the entire header width, hiding the title.

Title of back button is long enough to truncate the header title

Title of back button is long enough to remove the header title and back button icon

Steps to reproduce

  1. Create a screen with a long enough title
  2. From the newly created screen, navigate to another screen with default screen options
  3. Observe the long title of the back button

Snack or a link to a repository

https://github.com/DrOverbuild/BackButtonRepro

Screens version

3.17.0

React Native version

0.70.0

Platforms

iOS

JavaScript runtime

Hermes

Workflow

React Native (without Expo)

Architecture

Paper (Old Architecture)

Build type

Debug mode

Device

iOS simulator

Device model

iPhone 13 (iOS 15.5)

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 Sep 13, 2022
@DrOverbuild
Copy link
Author

DrOverbuild commented Sep 13, 2022

After a whole afternoon of investigation I have found the issue. There is a system issue where the back button does not change to "Back" if the navigation item's backButtonTitle is customized or the navigation item has a custom backBarButtonItem.

Even if there is no headerBackTitle prop set, in this block of code, config.backTitle is set to the value of the title of the previous screen when the screen options are updated for screen 2. Therefore, a custom RNSUIBarButtonItem is created and the navigation item's backBarButtonItem is set to that.

I have found that this is only caused when I use createNativeStackNavigator from @react-navigation/native-stack, which is the recommended way to import. If I import createNativeStackNavigator from react-native-screens/native-stack, the correct behavior is achieved if I don't customize the style of the back button.

I think we can still take a look at keeping this behavior if the style is changed.

We may want to look at using UINavigationBarAppearance for iOS 13.0 and newer to change the style of the back button. With the following snippet I was able change the font of the back button while maintaining system behavior:

NSMutableDictionary *attrs = [NSMutableDictionary new];
attrs[NSFontAttributeName] = [UIFont fontWithName:@"AvenirNextCondensed-DemiBoldItalic" size:17];
UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init];
appearance.backButtonAppearance.normal.titleTextAttributes = attrs;
navctr.navigationBar.scrollEdgeAppearance = appearance;
navctr.navigationBar.standardAppearance = appearance;

We'd probably want to combine something like that to lines 505-518.

Not sure if there's a solution for this for versions before iOS 13.0 or when the long press menu needs to be disabled.

I'd be happy to submit a PR but I wanted to discuss this a little first.

DrOverbuild added a commit to DrOverbuild/react-navigation that referenced this issue Sep 15, 2022
Unfortunately the changes from react-navigation#10761 caused an issue I reported on the
react-native-screens repo. (See software-mansion/react-native-screens#1589)

Essentially, the documentation states that per native behavior, the back button
on iOS will automatically use the title of the previous screen or "Back" if
there's not enough space. However, with the latest version of react-navigation,
the back button was the title of the previous screen no matter what. In some
cases, this caused the title of the current screen to be completely pushed off
the screen.

In UIKit this happens when the title of the back button is deliberately set, or
when a custom UIBarButtonItem is set as the back button. `react-native-screens`
creates a custom UIBarButtonItem when the header config component has a value
defined for `headerBackTitle`. So by leaving headerBackTitle undefined here, we
let the system automatically determine what to set as the back button title. In
my testing, system will choose the correct value even if the previous screen's
`title` or `headerTitle` is set differently from the route name.
satya164 pushed a commit to react-navigation/react-navigation that referenced this issue Sep 15, 2022
Unfortunately the changes from #10761 caused an issue I reported on the
react-native-screens repo. (See software-mansion/react-native-screens#1589)

Essentially, the documentation states that per native behavior, the back button
on iOS will automatically use the title of the previous screen or "Back" if
there's not enough space. However, with the latest version of react-navigation,
the back button was the title of the previous screen no matter what. In some
cases, this caused the title of the current screen to be completely pushed off
the screen.

In UIKit this happens when the title of the back button is deliberately set, or
when a custom UIBarButtonItem is set as the back button. `react-native-screens`
creates a custom UIBarButtonItem when the header config component has a value
defined for `headerBackTitle`. So by leaving headerBackTitle undefined here, we
let the system automatically determine what to set as the back button title. In
my testing, system will choose the correct value even if the previous screen's
`title` or `headerTitle` is set differently from the route name.
DrOverbuild added a commit to DrOverbuild/react-native-screens that referenced this issue Sep 16, 2022
Previously if a custom font or font size was set in the config, a new
`RNSUIBarButtonItem` would be created and set as the back bar button item.
However, this would break native behavior where the button title would shorten
to "Back" if there's not enough space. In iOS 13 it's possible to keep this
native behavior and also style the back button using `UINavigationBarAppearance`.

The only caveat with this approach is that because `UINavigationBarAppearance` is
not available prior to iOS 13, so on older systems we will go ahead and create
a custom `RNSUIBarButtonItem`.

This solves some issues mentioned in software-mansion#1589.
@h-five-e
Copy link

h-five-e commented Aug 15, 2023

This bug is (still) fixed in v3.20, but it breaks again in v3.21 and newer.

I have provided a repro here: https://github.com/h-five-e/repro-navigation-Ios-back-button-shows-long-title
Commit with just the relevant changes from a new app: h-five-e/repro-navigation-Ios-back-button-shows-long-title@51372e7

image image image

@h-five-e
Copy link

Hey @kkafar, I don't mean to badger you but given we talked about it here I wondered if you've seen this. Lmk if I should create a new issue instead!

@zetavg
Copy link
Contributor

zetavg commented Apr 14, 2024

I did some experimentation, and it seems this issue still exists with @react-navigation/native-stack v7 and "react-native-screens": "3.30.1" without RCT_NEW_ARCH_ENABLED.

I tested a few more versions and can confirm that it's working on v3.20 but broken in v3.21 and newer as @h-five-e stated, and the issue is still valid on 3.31.0-rc.1 even with createNativeStackNavigator imported from react-native-screens/native-stack.

  • "react-native-screens": "3.20.0"
  • "react-native-screens": "3.21.0"
  • "react-native-screens": "3.22.0"
  • "react-native-screens": "3.29.0"
  • "react-native-screens": "3.30.1"
  • "react-native-screens": "3.31.0-rc.1"
  • "react-native-screens": "3.31.0-rc.1" with createNativeStackNavigator imported from react-native-screens/native-stack
Considered as Working ✅ Working 1 Working 2
Considered as Broken ❌ Broken 1

@zetavg
Copy link
Contributor

zetavg commented Apr 14, 2024

After some investigation of the code, I think the cause is that in v3.21, the backBarButtonItem of UINavigationItem is always assigned a custom UIBarButtonItem, regardless of whether the back button is actually being customized, and assigning a custom UIBarButtonItem will remove the native behavior of automatically shortening the title to "Back" or hide the back title if there's not enough space.

As the desired behavior can be restored with this patch:

diff --git a/ios/RNSScreenStackHeaderConfig.mm b/ios/RNSScreenStackHeaderConfig.mm
index f7ec74678166f485a58ff3b8053a63befad14cfe..92adfb2874e027686c9bea82e5107c568bf5816f 100644
--- a/ios/RNSScreenStackHeaderConfig.mm
+++ b/ios/RNSScreenStackHeaderConfig.mm
@@ -511,8 +511,13 @@ namespace react = facebook::react;
                                                                              action:nil];
   [backBarButtonItem setMenuHidden:config.disableBackButtonMenu];
 
+  auto isBackButtonCustomized = !isBackTitleBlank || config.disableBackButtonMenu || NO;
+
   if (config.isBackTitleVisible) {
-    if (config.backTitleFontFamily || config.backTitleFontSize) {
+    if ((config.backTitleFontFamily &&
+         ![config.backTitleFontFamily isEqual:@"System"]) ||
+        config.backTitleFontSize) {
+      isBackButtonCustomized = YES;
       NSMutableDictionary *attrs = [NSMutableDictionary new];
       NSNumber *size = config.backTitleFontSize ?: @17;
       if (config.backTitleFontFamily) {
@@ -535,9 +540,17 @@ namespace react = facebook::react;
     // When backBarButtonItem's title is null, back menu will use value
     // of backButtonTitle
     [backBarButtonItem setTitle:nil];
+    isBackButtonCustomized = YES;
     prevItem.backButtonTitle = resolvedBackTitle;
   }
-  prevItem.backBarButtonItem = backBarButtonItem;
+
+  // Prevent unnecessary assignment of backBarButtonItem if it is not customized,
+  // as assigning one will override the native behavior of automatically shortening
+  // the title to "Back" or hide the back title if there's not enough space.
+  // See: https://github.com/software-mansion/react-native-screens/issues/1589
+  if (isBackButtonCustomized) {
+    prevItem.backBarButtonItem = backBarButtonItem;
+  }
 
   if (@available(iOS 11.0, *)) {
     if (config.largeTitle) {

kkafar added a commit that referenced this issue Apr 29, 2024
#2105)

## Description

Restore the iOS native behavior of automatically shorting the title of
the header back button to "Back" if there is not enough space, which is
documented
[here](https://reactnavigation.org/docs/header-buttons#customizing-the-back-button)[^1]
but does not behave as expected since v3.21.

Fixes #1589.


## Changes

* Assign to `backBarButtonItem` only if actual customizations of the
back button are being made.


## Screenshots / GIFs

| Before | After |
|--------|------|
| ![Broken
1](https://github.com/software-mansion/react-native-screens/assets/3784687/880eaecb-54d9-48d3-95bd-5f8e6cd7b066)
| ![Working
1](https://github.com/software-mansion/react-native-screens/assets/3784687/201e8006-544d-43ee-95e3-308e2f926566)
|


## Test code and steps to reproduce

<!--
Please include code that can be used to test this change and short
description how this example should work.
This snippet should be as minimal as possible and ready to be pasted
into editor (don't exclude exports or remove "not important" parts of
reproduction example)
-->

## Checklist

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

[^1]: According to the document, 'On iOS this includes a label next to
the button, which shows the title of the previous screen when the title
fits in the available space, otherwise it says "Back".'

---------

Co-authored-by: Kacper Kafara <kacperkafara@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants