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

fix(Paper,iOS): dismiss all attached view controllers correctly on reload #2175

Merged
merged 14 commits into from
Oct 28, 2024
151 changes: 151 additions & 0 deletions apps/src/tests/Test2175.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React from 'react';
import { Button, Modal, SafeAreaView, Text, View } from 'react-native';

import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

const Stack = createNativeStackNavigator();
const Tabs = createBottomTabNavigator();


function TabScreens({ navigation }): React.JSX.Element {
return (
<Tabs.Navigator>
<Tabs.Screen name='A' component={HomeScreen} />
<Tabs.Screen name='B' component={TabHomeScreen} />
</Tabs.Navigator>
);
}

function TabHomeScreen({ navigation }): React.JSX.Element {
return (
<View style={{ flex: 1, backgroundColor: 'lightcoral', justifyContent: 'center', }}>
<Text>Where do you where do you go, my lovely, oh oh oh oh</Text>
<Button title='Show owned transparentModal (outer navigator)' onPress={() => { navigation.navigate('TransparentModal') }} />
<Button title='Show owned modal (outer navigator)' onPress={() => { navigation.navigate('Modal') }} />
</View>
)
}


function HomeScreen({ navigation }): React.JSX.Element {
const [toggle, setToggle] = React.useState(false);
return (
<View style={{ flex: 1, backgroundColor: 'lightseagreen', justifyContent: 'center', }}>
<Text>Where do you where do you go, my lovely, oh oh oh oh</Text>
<Button title='Show owned transparentModal' onPress={() => { navigation.navigate('TransparentModal') }} />
<Button title='Show owned modal' onPress={() => { navigation.navigate('Modal') }} />
<Button title='Show tabs' onPress={() => { navigation.navigate('Tabs') }} />
<Button title='Show foreign modal' onPress={() => { setToggle(old => !old) }} />
<Modal
visible={toggle}
onRequestClose={() => setToggle(false)}
presentationStyle='formSheet'
animationType='slide'
>
<View style={{ flex: 1, backgroundColor: 'lightblue' }}>
<Text>Hello I'm a foreign modal</Text>
<Button title='Close foreign modal' onPress={() => { setToggle(false) }} />
</View>
</Modal>
</View>
)
}

function ModalScreen({ navigation }): React.JSX.Element {
const [toggle2, setToggle2] = React.useState(false);

return (
<View style={{ flex: 1, backgroundColor: 'lightcoral', opacity: 0.4 }}>
<Text>Where do you where do you go, my lovely, oh oh oh oh</Text>
<Button title='Go back' onPress={() => { navigation.goBack() }} />
<View style={{ width: '100%', height: 50, backgroundColor: 'red' }} />
<Button title='Push another Modal' onPress={() => { navigation.push('Modal') }} />
<Button title='Push foreign modal(inside Screen Component)' onPress={() => { navigation.push('ForeignModal')}} />
<Button title='Push foreign modal' onPress={() => { setToggle2(old => !old )}} />
<Modal
visible={toggle2}
onRequestClose={() => setToggle2(false)}
presentationStyle='formSheet'
animationType='slide'
>
<View style={{ flex: 1, backgroundColor: 'lightblue' }}>
<Text>Hello I'm a foreign modal</Text>
<Button title='Close foreign modal' onPress={() => { setToggle2(false) }} />
</View>
</Modal>
</View>
)
}

function ForeignModal({ navigation }): React.JSX.Element | null {
const [toggle, setToggle] = React.useState(false);
return (
<Modal
visible={toggle}
onRequestClose={() => setToggle(false)}
presentationStyle='formSheet'
animationType='slide'
>
<View style={{ flex: 1, backgroundColor: 'lightblue' }}>
<Text>Hello I'm a foreign modal</Text>
<Button title='Close foreign modal' onPress={() => { setToggle(false) }} />
</View>
</Modal>
)
}


const TestScreen = ({ navigation }): React.JSX.Element => {
return (
<SafeAreaView>
<Button
title={
'Click me and drag around a bit and I should log something still'
}
onPress={() => {
console.log(Date.now());
}}
/>
<Button
title={'Navigate to modal'}
onPress={() => {
navigation.navigate('Test2');
}}
/>
</SafeAreaView>
);
};

function App(): React.JSX.Element {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Tabs'>
<Stack.Screen name='Tabs' component={TabScreens} options={{ headerShown: false }} />
<Stack.Screen
name="Home"
component={HomeScreen}
/>
<Stack.Screen
name="TransparentModal"
component={ModalScreen}
options={{
presentation: 'transparentModal',
}}
/>
<Stack.Screen
name="Modal"
component={ModalScreen}
options={{
presentation: 'modal',
headerShown: true,
}}
/>
<Stack.Screen name={"ForeignModal"} component={ForeignModal} />
</Stack.Navigator>
</NavigationContainer>
);
}

export default App;
1 change: 1 addition & 0 deletions apps/src/tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export { default as Test2028 } from './Test2028';
export { default as Test2048 } from './Test2048';
export { default as Test2069 } from './Test2069';
export { default as Test2118 } from './Test2118';
export { default as Test2175 } from './Test2175';
export { default as Test2184 } from './Test2184';
export { default as Test2223 } from './Test2223';
export { default as Test2227 } from './Test2227';
Expand Down
19 changes: 16 additions & 3 deletions ios/RNSScreenStack.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1273,12 +1273,25 @@ - (void)prepareForRecycle
- (void)invalidate
{
_invalidated = YES;
for (UIViewController *controller in _presentedModals) {
[self dismissAllRelatedModals];
[_controller willMoveToParentViewController:nil];
[_controller removeFromParentViewController];
}

// This method aims to dismiss all modals for which presentation process
// has been initiated in this navigation controller, i. e. either a Screen
// with modal presentation or foreign modal presented from inside a Screen.
- (void)dismissAllRelatedModals
{
[_controller dismissViewControllerAnimated:NO completion:nil];

// This loop seems to be excessive. Above message send to `_controller` should
// be enough, because system dismisses the controllers recursively,
// however better safe than sorry & introduce a regression, thus it is left here.
for (UIViewController *controller in [_presentedModals reverseObjectEnumerator]) {
[controller dismissViewControllerAnimated:NO completion:nil];
}
[_presentedModals removeAllObjects];
[_controller willMoveToParentViewController:nil];
[_controller removeFromParentViewController];
}

#endif // RCT_NEW_ARCH_ENABLED
Expand Down
11 changes: 4 additions & 7 deletions src/components/Screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -373,15 +373,12 @@ export const InnerScreen = React.forwardRef<View, ScreenProps>(
// e.g. to use `useReanimatedTransitionProgress` (see `reanimated` folder in repo)
export const ScreenContext = React.createContext(InnerScreen);

const Screen = React.forwardRef<View, ScreenProps>(
(props, ref) => {
const ScreenWrapper = React.useContext(ScreenContext) || InnerScreen;
const Screen = React.forwardRef<View, ScreenProps>((props, ref) => {
const ScreenWrapper = React.useContext(ScreenContext) || InnerScreen;

return <ScreenWrapper {...props} ref={ref} />;
}
);
return <ScreenWrapper {...props} ref={ref} />;
});

Screen.displayName = 'Screen';


export default Screen;
Loading