diff --git a/TestsExample/App.js b/TestsExample/App.js
index cdd5348eb0..6740a81b81 100644
--- a/TestsExample/App.js
+++ b/TestsExample/App.js
@@ -4,6 +4,7 @@ import React from 'react';
import {enableFreeze} from 'react-native-screens';
import {ReanimatedScreenProvider} from 'react-native-screens/reanimated';
+import TestModalPresentation from './src/test-modal-presentation';
import Test42 from './src/Test42';
import Test111 from './src/Test111';
import Test263 from './src/Test263';
@@ -84,7 +85,7 @@ enableFreeze(true);
export default function App() {
return (
-
+
);
}
diff --git a/TestsExample/package.json b/TestsExample/package.json
index 08a33722c8..05dcdeb73e 100644
--- a/TestsExample/package.json
+++ b/TestsExample/package.json
@@ -14,11 +14,12 @@
"@react-navigation/bottom-tabs": "^6.3.1",
"@react-navigation/drawer": "^6.4.1",
"@react-navigation/native": "^6.0.10",
+ "@react-navigation/native-stack": "^6.7.0",
"@react-navigation/stack": "^6.2.1",
"nanoid": "^3.2.0",
+ "postinstall-postinstall": "^2.1.0",
"react": "18.0.0",
"react-native": "0.69.0",
- "postinstall-postinstall": "^2.1.0",
"react-native-gesture-handler": "^2.5.0",
"react-native-reanimated": "2.9.1",
"react-native-safe-area-context": "^4.0.1-rc.5",
diff --git a/TestsExample/src/test-modal-presentation/ModalGroup.tsx b/TestsExample/src/test-modal-presentation/ModalGroup.tsx
new file mode 100644
index 0000000000..989d5c8447
--- /dev/null
+++ b/TestsExample/src/test-modal-presentation/ModalGroup.tsx
@@ -0,0 +1,9 @@
+import StackBuilder from './StackBuilder';
+import Modals from './modals';
+
+
+export default StackBuilder(Modals, {
+ headerShown: false,
+ animation: 'fade_from_bottom',
+ presentation: 'containedTransparentModal',
+});
diff --git a/TestsExample/src/test-modal-presentation/ScreenGroup.tsx b/TestsExample/src/test-modal-presentation/ScreenGroup.tsx
new file mode 100644
index 0000000000..9f747014c7
--- /dev/null
+++ b/TestsExample/src/test-modal-presentation/ScreenGroup.tsx
@@ -0,0 +1,11 @@
+import StackBuilder from './StackBuilder';
+import Screens from './screens';
+
+
+export default StackBuilder(Screens, {
+ headerShadowVisible: false,
+ headerTintColor: 'black',
+ headerBackTitleVisible: false,
+ fullScreenGestureEnabled: true,
+ contentStyle: { backgroundColor: 'white' },
+});
diff --git a/TestsExample/src/test-modal-presentation/StackBuilder.tsx b/TestsExample/src/test-modal-presentation/StackBuilder.tsx
new file mode 100644
index 0000000000..df0497c1e6
--- /dev/null
+++ b/TestsExample/src/test-modal-presentation/StackBuilder.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import { createNativeStackNavigator, NativeStackNavigationOptions } from '@react-navigation/native-stack';
+
+export const NativeStack = createNativeStackNavigator();
+
+export default (configs: any, groupOptions?: NativeStackNavigationOptions) => {
+ return () => (
+
+ {configs.map(config => {
+ const { options, ...anyConfig } = config;
+ const { statusBarStyle, statusBarAnimation, statusBarHidden, ...anyOption } = options || {};
+ return ;
+ })}
+
+ );
+};
diff --git a/TestsExample/src/test-modal-presentation/index.tsx b/TestsExample/src/test-modal-presentation/index.tsx
new file mode 100644
index 0000000000..274a333029
--- /dev/null
+++ b/TestsExample/src/test-modal-presentation/index.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import {
+ NavigationContainer as NavigationContainerNative,
+} from '@react-navigation/native';
+
+import ModalGroup from './ModalGroup';
+import ScreenGroup from './ScreenGroup';
+import { NativeStack } from './StackBuilder';
+
+export default function TestModalPresentation() {
+ return (
+
+
+ {ScreenGroup()}
+ {ModalGroup()}
+
+
+ );
+}
diff --git a/TestsExample/src/test-modal-presentation/modals/ModalA.tsx b/TestsExample/src/test-modal-presentation/modals/ModalA.tsx
new file mode 100644
index 0000000000..319c12d3da
--- /dev/null
+++ b/TestsExample/src/test-modal-presentation/modals/ModalA.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { Button, Text, View } from 'react-native';
+
+type Props = {};
+
+export default function ModalA(props: Props) {
+ return (
+
+
+
+ ModalA, with opacity and backgroundColor
+ At ModalA, we still can gesture swipe the screenB back to screenA,
+
+
+ );
+}
diff --git a/TestsExample/src/test-modal-presentation/modals/index.tsx b/TestsExample/src/test-modal-presentation/modals/index.tsx
new file mode 100644
index 0000000000..9a7df758df
--- /dev/null
+++ b/TestsExample/src/test-modal-presentation/modals/index.tsx
@@ -0,0 +1,9 @@
+import ModalA from './ModalA';
+
+export default [
+ {
+ name: 'modalA',
+ component: ModalA,
+ options: { animation: 'fade' },
+ }
+]
diff --git a/TestsExample/src/test-modal-presentation/screens/ScreenA.tsx b/TestsExample/src/test-modal-presentation/screens/ScreenA.tsx
new file mode 100644
index 0000000000..482b76c1db
--- /dev/null
+++ b/TestsExample/src/test-modal-presentation/screens/ScreenA.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import { Button, Text, View } from 'react-native';
+
+export default function ScreenA({navigation}) {
+ return (
+
+ ScreenA, with backgroundColor: 'blue'
+
+ );
+}
diff --git a/TestsExample/src/test-modal-presentation/screens/ScreenB.tsx b/TestsExample/src/test-modal-presentation/screens/ScreenB.tsx
new file mode 100644
index 0000000000..b20aa5f9f8
--- /dev/null
+++ b/TestsExample/src/test-modal-presentation/screens/ScreenB.tsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import { Button, Text, View } from 'react-native';
+
+
+export default function ScreenB({navigation}) {
+ return (
+
+ ScreenB, with backgroundColor: 'cyan'
+
+ );
+}
diff --git a/TestsExample/src/test-modal-presentation/screens/index.tsx b/TestsExample/src/test-modal-presentation/screens/index.tsx
new file mode 100644
index 0000000000..6202079841
--- /dev/null
+++ b/TestsExample/src/test-modal-presentation/screens/index.tsx
@@ -0,0 +1,12 @@
+import ScreenA from './ScreenA';
+import ScreenB from './ScreenB';
+export default [
+ {
+ name: 'screenA',
+ component: ScreenA,
+ },
+ {
+ name: 'screenB',
+ component: ScreenB,
+ }
+]
diff --git a/TestsExample/yarn.lock b/TestsExample/yarn.lock
index b03d0e852d..ab2bc06a8d 100644
--- a/TestsExample/yarn.lock
+++ b/TestsExample/yarn.lock
@@ -1388,6 +1388,19 @@
resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.3.tgz#9f56b650a9a1a8263a271628be7342c8121d1788"
integrity sha512-Lv2lR7si5gNME8dRsqz57d54m4FJtrwHRjNQLOyQO546ZxO+g864cSvoLC6hQedQU0+IJnPTsZiEI2hHqfpEpw==
+"@react-navigation/elements@^1.3.4":
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.4.tgz#abb48136508c1e60862dc14a101ce02ff685a337"
+ integrity sha512-O0jICpjn3jskVo4yiWzZozmj7DZy1ZBbn3O7dbenuUjZSj/cscjwaapmZZFGcI/IMmjmx8UTKsybhCFEIbGf3g==
+
+"@react-navigation/native-stack@^6.7.0":
+ version "6.7.0"
+ resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-6.7.0.tgz#57e6390e3e4e46b3a7f580810c3d384b4676b7e5"
+ integrity sha512-03CuSwbBvP9+iXgjrTRRw+aog+KZXbhPzqCWVExzIWNOzf5/PJEcdtlm9KDrx2aHHDtDA6LRLKQA4UIlJOmNzA==
+ dependencies:
+ "@react-navigation/elements" "^1.3.4"
+ warn-once "^0.1.0"
+
"@react-navigation/native@^6.0.10":
version "6.0.10"
resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-6.0.10.tgz#c58aa176eb0e63f3641c83a65c509faf253e4385"
diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm
index 47207ef719..353d91d4da 100644
--- a/ios/RNSScreenStack.mm
+++ b/ios/RNSScreenStack.mm
@@ -599,6 +599,17 @@ - (void)cancelTouchesInParent
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
RNSScreenView *topScreen = (RNSScreenView *)_controller.viewControllers.lastObject.view;
+ RNSScreenView *topModal = (RNSScreenView *)_presentedModals.lastObject.view;
+
+ if (![topScreen isKindOfClass:[RNSScreenView class]] || !topScreen.gestureEnabled ||
+ _controller.viewControllers.count < 2) {
+ return NO;
+ }
+
+ BOOL isAttached = topModal.controller.presentingViewController != nil;
+ if (topModal.controller == topScreen.controller.presentedViewController && isAttached) {
+ return NO;
+ }
#if TARGET_OS_TV
[self cancelTouchesInParent];
@@ -820,12 +831,18 @@ - (BOOL)isScrollViewPanGestureRecognizer:(UIGestureRecognizer *)gestureRecognize
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveEvent:(UIEvent *)event
{
RNSScreenView *topScreen = (RNSScreenView *)_controller.viewControllers.lastObject.view;
+ RNSScreenView *topModal = (RNSScreenView *)_presentedModals.lastObject.view;
if (![topScreen isKindOfClass:[RNSScreenView class]] || !topScreen.gestureEnabled ||
_controller.viewControllers.count < 2) {
return NO;
}
+ BOOL isAttached = topModal.controller.presentingViewController != nil;
+ if (topModal.controller == topScreen.controller.presentedViewController && isAttached) {
+ return NO;
+ }
+
// We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled.
if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
return topScreen.fullScreenSwipeEnabled;