Skip to content

Commit 7334084

Browse files
authored
fix(iOS, Stack v4): hide back button when root screen is replaced with pop animation (#3770)
## Description Fixes replacing root screen with `pop` animation by hiding back button. Previously, back button would appear during the animation even though there was no reason for it to be visible. This is closely related how we're handling replace with `pop` animation (we're adding previous top controller and immediately popping it with animation; UIKit "thinks" that there is a screen to pop to so it displays back button). Thanks to @satya164 for reporting the issue. When testing the change I discovered another bug - when replacing non-root screen, back button title disappears: https://github.com/user-attachments/assets/7e45f71b-9df6-4a11-847e-75fe43618da0 This might be a native bug, I added a ticket on our internal board to investigate: software-mansion/react-native-screens-labs#1053. ## Changes - add a check for root pop replace and hide back button in `setPushViewControllers` ## Before & after - visual documentation | Before | After | | --- | --- | | <video src="https://github.com/user-attachments/assets/0e42e2d7-d71d-4805-b0b0-8ea3fc3b978a" /> | <video src="https://github.com/user-attachments/assets/81d1ce5f-b23c-40fa-ab55-05c18e266604" /> | ## Test plan Run `Test3770.tsx`. ## Checklist - [x] Included code example that can be used to test this change. - [x] For visual changes, included screenshots / GIFs / recordings documenting the change. - [ ] Ensured that CI passes
1 parent 22fe56e commit 7334084

3 files changed

Lines changed: 77 additions & 0 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react';
2+
import { NavigationContainer, ParamListBase } from '@react-navigation/native';
3+
import {
4+
NativeStackNavigationProp,
5+
createNativeStackNavigator,
6+
} from '@react-navigation/native-stack';
7+
import { Button, ScrollView } from 'react-native';
8+
import LongText from '../../shared/LongText';
9+
10+
type RouteParamList = {
11+
Screen1: undefined;
12+
Screen2: undefined;
13+
};
14+
15+
type NavigationProp<ParamList extends ParamListBase> = {
16+
navigation: NativeStackNavigationProp<ParamList>;
17+
};
18+
19+
type StackNavigationProp = NavigationProp<RouteParamList>;
20+
21+
const Stack = createNativeStackNavigator<RouteParamList>();
22+
23+
function Screen1({ navigation }: StackNavigationProp) {
24+
return (
25+
<ScrollView contentInsetAdjustmentBehavior="automatic">
26+
<Button
27+
title="Replace with Screen2"
28+
onPress={() => navigation.replace('Screen2')}
29+
/>
30+
<LongText />
31+
</ScrollView>
32+
);
33+
}
34+
35+
function Screen2({ navigation }: StackNavigationProp) {
36+
return (
37+
<ScrollView contentInsetAdjustmentBehavior="automatic">
38+
<Button
39+
title="Replace with Screen1"
40+
onPress={() => navigation.replace('Screen1')}
41+
/>
42+
<LongText />
43+
</ScrollView>
44+
);
45+
}
46+
47+
export default function App() {
48+
return (
49+
<NavigationContainer>
50+
<Stack.Navigator>
51+
<Stack.Screen
52+
name="Screen1"
53+
component={Screen1}
54+
options={{ headerShown: false, animationTypeForReplace: 'pop' }}
55+
/>
56+
<Stack.Screen
57+
name="Screen2"
58+
component={Screen2}
59+
options={{
60+
headerLargeTitleEnabled: true,
61+
headerTransparent: true,
62+
}}
63+
/>
64+
</Stack.Navigator>
65+
</NavigationContainer>
66+
);
67+
}

apps/src/tests/issue-tests/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ export { default as Test3596 } from './Test3596';
182182
export { default as Test3611 } from './Test3611';
183183
export { default as Test3617 } from './Test3617';
184184
export { default as Test3760 } from './Test3760';
185+
export { default as Test3770 } from './Test3770';
185186
export { default as TestScreenAnimation } from './TestScreenAnimation';
186187
// The following test was meant to demo the "go back" gesture using Reanimated
187188
// but the associated PR in react-navigation is currently put on hold

ios/RNSScreenStack.mm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,15 @@ - (void)setPushViewControllers:(NSArray<UIViewController *> *)controllers
735735
// added the last top element to it and perform (animated) pop
736736
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:controllers];
737737
[newControllers addObject:previousTop];
738+
739+
#if !TARGET_OS_TV
740+
// If we're replacing root screen, there should be no back button.
741+
BOOL isRootReplace = _controller.viewControllers.count == 1 && controllers.count == 1;
742+
if (isRootReplace) {
743+
previousTop.navigationItem.hidesBackButton = YES;
744+
}
745+
#endif // !TARGET_OS_TV
746+
738747
[_controller setViewControllers:newControllers animated:NO];
739748
[_controller popViewControllerAnimated:YES];
740749
}

0 commit comments

Comments
 (0)