Skip to content

Commit

Permalink
fix(iOS): select correct VC for nested modal presentation (software-m…
Browse files Browse the repository at this point in the history
…ansion#1912)

## Description

Closes software-mansion#1829

Currently it is not possible to navigate to a modal from another modal
view that is mounted in JS under nested stack (see software-mansion#1829 description for
better context).
This is the case, because modal is presented by
`RNSNavigationController` corresponding to the stack under which it is
mounted in JS ==> UIKit reports that such controller is already
presenting.

IMO the last presented modal should be the one, to present new one.


## Changes

Added code handling above case, by checking whether modals from other
stacks are present & using top-most modal for presentation in such case.

## Test code and steps to reproduce

See `Test1829` in test example apps

## Checklist

- [x] Included code example that can be used to test this change
- [x] Ensured that CI passes (merged only comments after final CI
checks)
  • Loading branch information
kkafar authored and ja1ns committed Oct 9, 2024
1 parent 1a5a790 commit af5626c
Show file tree
Hide file tree
Showing 17 changed files with 619 additions and 10 deletions.
1 change: 1 addition & 0 deletions FabricTestExample/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import Test1671 from './src/Test1671';
import Test1683 from './src/Test1683';
import Test1726 from './src/Test1726';
import Test1802 from './src/Test1802';
import Test1829 from './src/Test1829';
import Test1844 from './src/Test1844';
import Test1864 from './src/Test1864';

Expand Down
15 changes: 15 additions & 0 deletions FabricTestExample/src/Test1829/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { NavigationContainer } from '@react-navigation/native';

import Stacks from './navigation/Stacks';

const App = () => (
<SafeAreaProvider>
<NavigationContainer>
<Stacks />
</NavigationContainer>
</SafeAreaProvider>
);

export default App;
47 changes: 47 additions & 0 deletions FabricTestExample/src/Test1829/navigation/Stacks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import HomeView from '../screens/HomeView';
import ModalView from '../screens/ModalView';
import BaseView from '../screens/InnerView';
import InnerModal from '../screens/InnerModal';

const RootStack = createNativeStackNavigator();
const InnerStack = createNativeStackNavigator();

const Inner = () => (
<InnerStack.Navigator>
<InnerStack.Screen name="base" component={BaseView} />
<InnerStack.Screen
name="inner-modal"
component={InnerModal}
options={{ presentation: 'modal' }}
/>
</InnerStack.Navigator>
);

const Stacks = () => (
<RootStack.Navigator
screenOptions={{
headerShown: false,
}}>
<RootStack.Screen name="home" component={HomeView} />
<RootStack.Screen name="inner" component={Inner} />
<RootStack.Screen
name="modal"
component={ModalView}
options={{
presentation: 'modal',
}}
/>
<RootStack.Screen
name="modal-2"
component={ModalView}
options={{
presentation: 'modal',
}}
/>
</RootStack.Navigator>
);

export default Stacks;
17 changes: 17 additions & 0 deletions FabricTestExample/src/Test1829/screens/CardView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import { TouchableOpacity, View, Text, StyleSheet } from 'react-native';

const CardView = () => {
return <View style={styles.container} />;
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'blue',
},
});

export default CardView;
43 changes: 43 additions & 0 deletions FabricTestExample/src/Test1829/screens/HomeView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import { TouchableOpacity, View, Text, StyleSheet } from 'react-native';
import { useNavigation } from '@react-navigation/native';

const HomeView = () => {
const navigation = useNavigation();
return (
<View style={styles.container}>
<TouchableOpacity onPress={() => navigation.navigate('inner')}>
<View style={styles.button}>
<Text style={styles.buttonText}>Open Inner Navigator</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => navigation.navigate('modal')}>
<View style={styles.button}>
<Text style={styles.buttonText}>Open Outer Modal</Text>
</View>
</TouchableOpacity>
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'yellow',
},
button: {
width: 200,
height: 50,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 5,
backgroundColor: 'blue',
},
buttonText: {
color: 'white',
},
});

export default HomeView;
47 changes: 47 additions & 0 deletions FabricTestExample/src/Test1829/screens/InnerModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { TouchableOpacity, View, Text, StyleSheet } from 'react-native';
import { useNavigation } from '@react-navigation/native';

const HomeView = () => {
const navigation = useNavigation();
return (
<View style={styles.container}>
<Text>
This is the inner modal. Opening the outer modal here does not work.
WTF?
</Text>
<TouchableOpacity onPress={() => navigation.navigate('modal')}>
<View style={styles.button}>
<Text style={styles.buttonText}>Open Outer Modal</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => navigation.goBack()}>
<View style={styles.button}>
<Text style={styles.buttonText}>Back</Text>
</View>
</TouchableOpacity>
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'yellow',
},
button: {
width: 200,
height: 50,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 5,
backgroundColor: 'blue',
},
buttonText: {
color: 'white',
},
});

export default HomeView;
51 changes: 51 additions & 0 deletions FabricTestExample/src/Test1829/screens/InnerView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import { TouchableOpacity, View, Text, StyleSheet } from 'react-native';
import { useNavigation } from '@react-navigation/native';

const HomeView = () => {
const navigation = useNavigation();
return (
<View style={styles.container}>
<Text>
This is the inner navigator. Opening the outer modal here works!
</Text>
<TouchableOpacity onPress={() => navigation.navigate('inner-modal')}>
<View style={styles.button}>
<Text style={styles.buttonText}>Open Inner Modal</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => navigation.navigate('modal')}>
<View style={styles.button}>
<Text style={styles.buttonText}>Open Outer Modal</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => navigation.goBack()}>
<View style={styles.button}>
<Text style={styles.buttonText}>Go back</Text>
</View>
</TouchableOpacity>
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'yellow',
},
button: {
width: 200,
height: 50,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 5,
backgroundColor: 'blue',
},
buttonText: {
color: 'white',
},
});

export default HomeView;
53 changes: 53 additions & 0 deletions FabricTestExample/src/Test1829/screens/ModalView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useEffect } from 'react';
import { TouchableOpacity, View, Text, StyleSheet } from 'react-native';
import { useNavigation } from '@react-navigation/native';

const ModalView = () => {
useEffect(() => {
console.log('ModalView mounted');
}, []);

const navigation = useNavigation();
return (
<View style={styles.container}>
<Text>
This is the outer modal. If you try to open it from the inner modal, it
doesn't work. But if you comment out the `presentation: modal` on the
inner modal, it does work! In addition, if opening a modal in the same
navigator stack also works.
</Text>
<TouchableOpacity onPress={() => navigation.navigate('modal-2')}>
<View style={styles.button}>
<Text style={styles.buttonText}>Open sibling outer modal.</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => navigation.goBack()}>
<View style={styles.button}>
<Text style={styles.buttonText}>Back</Text>
</View>
</TouchableOpacity>
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'green',
},
button: {
width: 200,
height: 50,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 5,
backgroundColor: 'blue',
},
buttonText: {
color: 'white',
},
});

export default ModalView;
1 change: 1 addition & 0 deletions TestsExample/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import Test1791 from './src/Test1791';
import Test1802 from './src/Test1802';
import Test1844 from './src/Test1844';
import Test1864 from './src/Test1864';
import Test1829 from './src/Test1829';

enableFreeze(true);

Expand Down
15 changes: 15 additions & 0 deletions TestsExample/src/Test1829/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { NavigationContainer } from '@react-navigation/native';

import Stacks from './navigation/Stacks';

const App = () => (
<SafeAreaProvider>
<NavigationContainer>
<Stacks />
</NavigationContainer>
</SafeAreaProvider>
);

export default App;
47 changes: 47 additions & 0 deletions TestsExample/src/Test1829/navigation/Stacks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import HomeView from '../screens/HomeView';
import ModalView from '../screens/ModalView';
import BaseView from '../screens/InnerView';
import InnerModal from '../screens/InnerModal';

const RootStack = createNativeStackNavigator();
const InnerStack = createNativeStackNavigator();

const Inner = () => (
<InnerStack.Navigator>
<InnerStack.Screen name="base" component={BaseView} />
<InnerStack.Screen
name="inner-modal"
component={InnerModal}
options={{ presentation: 'modal' }}
/>
</InnerStack.Navigator>
);

const Stacks = () => (
<RootStack.Navigator
screenOptions={{
headerShown: false,
}}>
<RootStack.Screen name="home" component={HomeView} />
<RootStack.Screen name="inner" component={Inner} />
<RootStack.Screen
name="modal"
component={ModalView}
options={{
presentation: 'modal',
}}
/>
<RootStack.Screen
name="modal-2"
component={ModalView}
options={{
presentation: 'modal',
}}
/>
</RootStack.Navigator>
);

export default Stacks;
17 changes: 17 additions & 0 deletions TestsExample/src/Test1829/screens/CardView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import { TouchableOpacity, View, Text, StyleSheet } from 'react-native';

const CardView = () => {
return <View style={styles.container} />;
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'blue',
},
});

export default CardView;
Loading

0 comments on commit af5626c

Please sign in to comment.