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

feat(iOS): Add support for UINavigationBackButtonDisplayMode #2123

Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,8 @@ class ScreenStackHeaderConfigViewManager : ViewGroupManager<ScreenStackHeaderCon
override fun setDisableBackButtonMenu(view: ScreenStackHeaderConfig?, value: Boolean) {
logNotAvailable("disableBackButtonMenu")
}

override fun setBackButtonDisplayMode(view: ScreenStackHeaderConfig?, value: String?) {
logNotAvailable("backButtonDisplayMode")
}
Comment on lines +205 to +207
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it work fine? Because I do not see RNSScreenStackHeaderConfigManagerInterface<ScreenStackHeaderConfig> being updated.

What we need to do here is run this project on Android Fabric (or just trigger the codegen manually from Android Studio) & copy generated RNSScreenStackHeaderConfigManagerInterface<ScreenStackHeaderConfig> interface from build folders android/build/generate/source/codegen/java/com/facebook/react/viewmanagers/... to android/src/paper/java/com/facebook/react/viewmanagers/ so that the interfaces are up to date.

TODO: We need to automate this somehow.

}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ public void setProperty(T view, String propName, @Nullable Object value) {
case "disableBackButtonMenu":
mViewManager.setDisableBackButtonMenu(view, value == null ? false : (boolean) value);
break;
case "backButtonDisplayMode":
mViewManager.setBackButtonDisplayMode(view, (String) value);
break;
case "hideBackButton":
mViewManager.setHideBackButton(view, value == null ? false : (boolean) value);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public interface RNSScreenStackHeaderConfigManagerInterface<T extends View> {
void setTitleFontWeight(T view, @Nullable String value);
void setTitleColor(T view, @Nullable Integer value);
void setDisableBackButtonMenu(T view, boolean value);
void setBackButtonDisplayMode(T view, @Nullable String value);
void setHideBackButton(T view, boolean value);
void setBackButtonInCustomView(T view, boolean value);
void setTopInsetEnabled(T view, boolean value);
Expand Down
4 changes: 4 additions & 0 deletions guides/GUIDE_FOR_LIBRARY_AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,10 @@ Controls whether the stack should be in `rtl` or `ltr` form.

Boolean indicating whether to show the menu on longPress of iOS >= 14 back button.

### `backButtonDisplayMode` (iOS only)

Enum value indicating display mode of **default** back button. It works on iOS >= 14, and is used only when none of: `backTitleFontFamily`, `backTitleFontSize`, `disableBackButtonMenu` or `backTitle` is set. Otherwise, when the button is customized, under the hood we use iOS native `backButtonItem` which overrides `backButtonDisplayMode`. Read more [#2123](https://github.com/software-mansion/react-native-screens/pull/2123).
maciekstosio marked this conversation as resolved.
Show resolved Hide resolved

### `hidden`

When set to `true` the header will be hidden while the parent `Screen` is on the top of the stack. The default value is `false`.
Expand Down
6 changes: 6 additions & 0 deletions ios/RNSConvert.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ namespace react = facebook::react;

@interface RNSConvert : NSObject

+ (UISemanticContentAttribute)UISemanticContentAttributeFromCppEquivalent:
(react::RNSScreenStackHeaderConfigDirection)direction;

+ (UINavigationItemBackButtonDisplayMode)UINavigationItemBackButtonDisplayModeFromCppEquivalent:
(react::RNSScreenStackHeaderConfigBackButtonDisplayMode)backButtonDisplayMode;

+ (RNSScreenStackPresentation)RNSScreenStackPresentationFromCppEquivalent:
(react::RNSScreenStackPresentation)stackPresentation;

Expand Down
24 changes: 24 additions & 0 deletions ios/RNSConvert.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,30 @@
#ifdef RCT_NEW_ARCH_ENABLED
@implementation RNSConvert

+ (UISemanticContentAttribute)UISemanticContentAttributeFromCppEquivalent:
(react::RNSScreenStackHeaderConfigDirection)direction
{
switch (direction) {
case react::RNSScreenStackHeaderConfigDirection::Rtl:
return UISemanticContentAttributeForceRightToLeft;
case react::RNSScreenStackHeaderConfigDirection::Ltr:
return UISemanticContentAttributeForceLeftToRight;
}
}

+ (UINavigationItemBackButtonDisplayMode)UINavigationItemBackButtonDisplayModeFromCppEquivalent:
(react::RNSScreenStackHeaderConfigBackButtonDisplayMode)backButtonDisplayMode
{
switch (backButtonDisplayMode) {
case react::RNSScreenStackHeaderConfigBackButtonDisplayMode::Default:
return UINavigationItemBackButtonDisplayModeDefault;
case react::RNSScreenStackHeaderConfigBackButtonDisplayMode::Generic:
return UINavigationItemBackButtonDisplayModeGeneric;
case react::RNSScreenStackHeaderConfigBackButtonDisplayMode::Minimal:
return UINavigationItemBackButtonDisplayModeMinimal;
}
}

+ (RNSScreenStackPresentation)RNSScreenStackPresentationFromCppEquivalent:
(react::RNSScreenStackPresentation)stackPresentation
{
Expand Down
2 changes: 2 additions & 0 deletions ios/RNSScreenStackHeaderConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
@property (nonatomic) BOOL translucent;
@property (nonatomic) BOOL backButtonInCustomView;
@property (nonatomic) UISemanticContentAttribute direction;
@property (nonatomic) UINavigationItemBackButtonDisplayMode backButtonDisplayMode;

+ (void)willShowViewController:(UIViewController *)vc
animated:(BOOL)animated
Expand All @@ -70,5 +71,6 @@

+ (UIBlurEffectStyle)UIBlurEffectStyle:(id)json;
+ (UISemanticContentAttribute)UISemanticContentAttribute:(id)json;
+ (UINavigationItemBackButtonDisplayMode)UINavigationItemBackButtonDisplayMode:(id)json;

@end
37 changes: 23 additions & 14 deletions ios/RNSScreenStackHeaderConfig.mm
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#import <React/RCTFont.h>
#import <React/RCTImageLoader.h>
#import <React/RCTImageSource.h>
#import "RNSConvert.h"
#import "RNSScreen.h"
#import "RNSScreenStackHeaderConfig.h"
#import "RNSSearchBar.h"
Expand Down Expand Up @@ -513,6 +514,10 @@ + (void)updateViewController:(UIViewController *)vc

auto isBackButtonCustomized = !isBackTitleBlank || config.disableBackButtonMenu;

if (@available(iOS 14.0, *)) {
prevItem.backButtonDisplayMode = config.backButtonDisplayMode;
}
maciekstosio marked this conversation as resolved.
Show resolved Hide resolved

if (config.isBackTitleVisible) {
if ((config.backTitleFontFamily &&
// While being used by react-navigation, the `backTitleFontFamily` will
Expand Down Expand Up @@ -786,26 +791,16 @@ - (void)prepareForRecycle
_initialPropsSet = NO;
}

+ (react::ComponentDescriptorProvider)componentDescriptorProvider
{
return react::concreteComponentDescriptorProvider<react::RNSScreenStackHeaderConfigComponentDescriptor>();
}

- (NSNumber *)getFontSizePropValue:(int)value
{
if (value > 0)
return [NSNumber numberWithInt:value];
return nil;
}

- (UISemanticContentAttribute)getDirectionPropValue:(react::RNSScreenStackHeaderConfigDirection)direction
+ (react::ComponentDescriptorProvider)componentDescriptorProvider
{
switch (direction) {
case react::RNSScreenStackHeaderConfigDirection::Rtl:
return UISemanticContentAttributeForceRightToLeft;
case react::RNSScreenStackHeaderConfigDirection::Ltr:
return UISemanticContentAttributeForceLeftToRight;
}
return react::concreteComponentDescriptorProvider<react::RNSScreenStackHeaderConfigComponentDescriptor>();
}

- (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::Shared const &)oldProps
Expand Down Expand Up @@ -852,9 +847,11 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::
_backTitleFontSize = [self getFontSizePropValue:newScreenProps.backTitleFontSize];
_hideBackButton = newScreenProps.hideBackButton;
_disableBackButtonMenu = newScreenProps.disableBackButtonMenu;
_backButtonDisplayMode =
[RNSConvert UINavigationItemBackButtonDisplayModeFromCppEquivalent:newScreenProps.backButtonDisplayMode];

if (newScreenProps.direction != oldScreenProps.direction) {
_direction = [self getDirectionPropValue:newScreenProps.direction];
_direction = [RNSConvert UISemanticContentAttributeFromCppEquivalent:newScreenProps.direction];
}

_backTitleVisible = newScreenProps.backTitleVisible;
Expand Down Expand Up @@ -945,7 +942,9 @@ - (UIView *)view
RCT_EXPORT_VIEW_PROPERTY(hideShadow, BOOL)
RCT_EXPORT_VIEW_PROPERTY(backButtonInCustomView, BOOL)
RCT_EXPORT_VIEW_PROPERTY(disableBackButtonMenu, BOOL)
// `hidden` is an UIView property, we need to use different name internally
RCT_EXPORT_VIEW_PROPERTY(
backButtonDisplayMode,
UINavigationItemBackButtonDisplayMode) // `hidden` is an UIView property, we need to use different name internally
maciekstosio marked this conversation as resolved.
Show resolved Hide resolved
RCT_REMAP_VIEW_PROPERTY(hidden, hide, BOOL)
RCT_EXPORT_VIEW_PROPERTY(translucent, BOOL)

Expand Down Expand Up @@ -1002,6 +1001,16 @@ + (NSMutableDictionary *)blurEffectsForIOSVersion
UISemanticContentAttributeUnspecified,
integerValue)

RCT_ENUM_CONVERTER(
UINavigationItemBackButtonDisplayMode,
(@{
@"default" : @(UINavigationItemBackButtonDisplayModeDefault),
@"generic" : @(UINavigationItemBackButtonDisplayModeGeneric),
@"minimal" : @(UINavigationItemBackButtonDisplayModeMinimal),
}),
UINavigationItemBackButtonDisplayModeDefault,
integerValue)

RCT_ENUM_CONVERTER(UIBlurEffectStyle, ([self blurEffectsForIOSVersion]), UIBlurEffectStyleExtraLight, integerValue)

@end
4 changes: 4 additions & 0 deletions native-stack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ String that applies `rtl` or `ltr` form to the stack. On Android, you have to ad

Boolean indicating whether to show the menu on longPress of iOS >= 14 back button.

#### `backButtonDisplayMode` (iOS only)

Enum value indicating display mode of **default** back button. It works on iOS >= 14, and is used only when none of: `backTitleFontFamily`, `backTitleFontSize`, `disableBackButtonMenu` or `backTitle` is set. Otherwise, when the button is customized, under the hood we use iOS native `backButtonItem` which overrides `backButtonDisplayMode`. Read more [#2123](https://github.com/software-mansion/react-native-screens/pull/2123).
maciekstosio marked this conversation as resolved.
Show resolved Hide resolved

#### `fullScreenSwipeEnabled` (iOS only)

Boolean indicating whether the swipe gesture should work on whole screen. Swiping with this option results in the same transition animation as `simple_push` by default. It can be changed to other custom animations with `customAnimationOnSwipe` prop, but default iOS swipe animation is not achievable due to usage of custom recognizer. Defaults to `false`.
Expand Down
3 changes: 3 additions & 0 deletions src/fabric/ScreenStackHeaderConfigNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type OnAttachedEvent = Readonly<{}>;
// eslint-disable-next-line @typescript-eslint/ban-types
type OnDetachedEvent = Readonly<{}>;

type BackButtonDisplayMode = 'minimal' | 'default' | 'generic';

export interface NativeProps extends ViewProps {
onAttached?: DirectEventHandler<OnAttachedEvent>;
onDetached?: DirectEventHandler<OnDetachedEvent>;
Expand All @@ -39,6 +41,7 @@ export interface NativeProps extends ViewProps {
titleFontWeight?: string;
titleColor?: ColorValue;
disableBackButtonMenu?: boolean;
backButtonDisplayMode?: WithDefault<BackButtonDisplayMode, 'default'>;
hideBackButton?: boolean;
backButtonInCustomView?: boolean;
// TODO: implement this props on iOS
Expand Down
9 changes: 9 additions & 0 deletions src/native-stack/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ export type NativeStackNavigationOptions = {
* @platform ios
*/
disableBackButtonMenu?: boolean;
/**
* How the back button behaves by default (when not customized). Available on iOS>=14, and is used only when none of: `backTitleFontFamily`, `backTitleFontSize`, `disableBackButtonMenu` or `backTitle` is set.
* The following values are currently supported (they correspond to https://developer.apple.com/documentation/uikit/uinavigationitembackbuttondisplaymode?language=objc):
* - "default" – show given back button previous controller title, system generic or just icon based on available space
* - "generic" – show given system generic or just icon based on available space
* - "minimal" – show just an icon
* @platform ios
kkafar marked this conversation as resolved.
Show resolved Hide resolved
*/
backButtonDisplayMode?: ScreenStackHeaderConfigProps['backButtonDisplayMode'];
/**
* Whether inactive screens should be suspended from re-rendering. Defaults to `false`.
* Defaults to `true` when `enableFreeze()` is run at the top of the application.
Expand Down
2 changes: 2 additions & 0 deletions src/native-stack/views/HeaderConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default function HeaderConfig({
backButtonInCustomView,
direction,
disableBackButtonMenu,
backButtonDisplayMode = 'default',
headerBackTitle,
headerBackTitleStyle = {},
headerBackTitleVisible = true,
Expand Down Expand Up @@ -120,6 +121,7 @@ export default function HeaderConfig({
color={tintColor}
direction={direction}
disableBackButtonMenu={disableBackButtonMenu}
backButtonDisplayMode={backButtonDisplayMode}
hidden={headerShown === false}
hideBackButton={headerHideBackButton}
hideShadow={headerHideShadow}
Expand Down
10 changes: 10 additions & 0 deletions src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type SearchBarCommands = {
cancelSearch: () => void;
};

export type BackButtonDisplayMode = 'default' | 'generic' | 'minimal';
export type StackPresentationTypes =
| 'push'
| 'modal'
Expand Down Expand Up @@ -461,6 +462,15 @@ export interface ScreenStackHeaderConfigProps extends ViewProps {
* @platform ios
*/
disableBackButtonMenu?: boolean;
/**
* How the back button behaves by default (when not customized). Available on iOS>=14, and is used only when none of: `backTitleFontFamily`, `backTitleFontSize`, `disableBackButtonMenu` or `backTitle` is set.
* The following values are currently supported (they correspond to https://developer.apple.com/documentation/uikit/uinavigationitembackbuttondisplaymode?language=objc):
* - "default" – show given back button previous controller title, system generic or just icon based on available space
* - "generic" – show given system generic or just icon based on available space
* - "minimal" – show just an icon
* @platform ios
kkafar marked this conversation as resolved.
Show resolved Hide resolved
*/
backButtonDisplayMode?: BackButtonDisplayMode;
/**
* When set to true the header will be hidden while the parent Screen is on the top of the stack. The default value is false.
*/
Expand Down
Loading