diff --git a/TestsExample/App.js b/TestsExample/App.js
index 9207cb2cdb..8e04dd995a 100644
--- a/TestsExample/App.js
+++ b/TestsExample/App.js
@@ -75,6 +75,7 @@ import Test1214 from './src/Test1214';
import Test1227 from './src/Test1227';
import Test1228 from './src/Test1228';
import Test1259 from './src/Test1259';
+import Test1260 from './src/Test1260';
enableFreeze(true);
diff --git a/TestsExample/src/Test1260.tsx b/TestsExample/src/Test1260.tsx
new file mode 100644
index 0000000000..a98cc7c965
--- /dev/null
+++ b/TestsExample/src/Test1260.tsx
@@ -0,0 +1,37 @@
+import * as React from 'react';
+import {Button, View} from 'react-native';
+import {NavigationContainer, ParamListBase} from '@react-navigation/native';
+import {createNativeStackNavigator, NativeStackNavigationProp} from 'react-native-screens/native-stack';
+
+const Stack = createNativeStackNavigator();
+
+export default function App() {
+ return (
+
+
+
+
+
+
+ );
+}
+
+function First({navigation}: {navigation: NativeStackNavigationProp}) {
+ return (
+
+
+
+ );
+}
+
+function Second({navigation}: {navigation: NativeStackNavigationProp}) {
+ return (
+
+
+ );
+}
diff --git a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md
index b63ec7c978..755fc4bfc3 100644
--- a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md
+++ b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md
@@ -180,6 +180,14 @@ Defaults to `auto`.
Sets the translucency of the status bar (similar to the `StatusBar` component). Defaults to `false`.
+### `swipeDirection` (iOS only)
+
+Sets the direction in which you should swipe to dismiss the screen. The following values are supported:
+- `vertical` – dismiss screen vertically
+- `horizontal` – dismiss screen horizontally (default)
+
+When using `vertical` option, options `fullScreenSwipeEnabled: true`, `customAnimationOnSwipe: true` and `stackAnimation: 'slide_from_bottom'` are set by default.
+
### `transitionDuration` (iOS only)
Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `350`.
diff --git a/ios/RNSScreen.h b/ios/RNSScreen.h
index a2bc1ba472..4aa61a55f8 100644
--- a/ios/RNSScreen.h
+++ b/ios/RNSScreen.h
@@ -29,6 +29,11 @@ typedef NS_ENUM(NSInteger, RNSScreenReplaceAnimation) {
RNSScreenReplaceAnimationPush,
};
+typedef NS_ENUM(NSInteger, RNSScreenSwipeDirection) {
+ RNSScreenSwipeDirectionVertical,
+ RNSScreenSwipeDirectionHorizontal,
+};
+
typedef NS_ENUM(NSInteger, RNSActivityState) {
RNSActivityStateInactive = 0,
RNSActivityStateTransitioningOrBelowTop = 1,
@@ -92,6 +97,7 @@ typedef NS_ENUM(NSInteger, RNSWindowTrait) {
@property (nonatomic) RNSScreenStackAnimation stackAnimation;
@property (nonatomic) RNSScreenStackPresentation stackPresentation;
@property (nonatomic) RNSScreenReplaceAnimation replaceAnimation;
+@property (nonatomic) RNSScreenSwipeDirection swipeDirection;
@property (nonatomic) BOOL preventNativeDismiss;
@property (nonatomic) BOOL hasOrientationSet;
@property (nonatomic) BOOL hasStatusBarStyleSet;
diff --git a/ios/RNSScreen.m b/ios/RNSScreen.m
index dba386a18d..dbf84b9e6d 100644
--- a/ios/RNSScreen.m
+++ b/ios/RNSScreen.m
@@ -793,6 +793,7 @@ @implementation RNSScreenManager
RCT_EXPORT_VIEW_PROPERTY(replaceAnimation, RNSScreenReplaceAnimation)
RCT_EXPORT_VIEW_PROPERTY(stackPresentation, RNSScreenStackPresentation)
RCT_EXPORT_VIEW_PROPERTY(stackAnimation, RNSScreenStackAnimation)
+RCT_EXPORT_VIEW_PROPERTY(swipeDirection, RNSScreenSwipeDirection)
RCT_EXPORT_VIEW_PROPERTY(transitionDuration, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(onAppear, RCTDirectEventBlock);
@@ -859,6 +860,15 @@ @implementation RCTConvert (RNSScreen)
RNSScreenReplaceAnimationPop,
integerValue)
+RCT_ENUM_CONVERTER(
+ RNSScreenSwipeDirection,
+ (@{
+ @"vertical" : @(RNSScreenSwipeDirectionVertical),
+ @"horizontal" : @(RNSScreenSwipeDirectionHorizontal),
+ }),
+ RNSScreenSwipeDirectionHorizontal,
+ integerValue)
+
#if !TARGET_OS_TV
RCT_ENUM_CONVERTER(
RNSStatusBarStyle,
diff --git a/ios/RNSScreenStack.m b/ios/RNSScreenStack.m
index 10ca117811..9548ff7224 100644
--- a/ios/RNSScreenStack.m
+++ b/ios/RNSScreenStack.m
@@ -650,13 +650,23 @@ - (void)setupGestureHandlers
- (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer
{
- float translation = [gestureRecognizer translationInView:gestureRecognizer.view].x;
- float velocity = [gestureRecognizer velocityInView:gestureRecognizer.view].x;
- float distance = gestureRecognizer.view.bounds.size.width;
- BOOL isRTL = _controller.view.semanticContentAttribute == UISemanticContentAttributeForceRightToLeft;
- if (isRTL) {
- translation = -translation;
- velocity = -velocity;
+ RNSScreenView *topScreen = (RNSScreenView *)_controller.viewControllers.lastObject.view;
+ float translation;
+ float velocity;
+ float distance;
+ if (topScreen.swipeDirection == RNSScreenSwipeDirectionVertical) {
+ translation = [gestureRecognizer translationInView:gestureRecognizer.view].y;
+ velocity = [gestureRecognizer velocityInView:gestureRecognizer.view].y;
+ distance = gestureRecognizer.view.bounds.size.height;
+ } else {
+ translation = [gestureRecognizer translationInView:gestureRecognizer.view].x;
+ velocity = [gestureRecognizer velocityInView:gestureRecognizer.view].x;
+ distance = gestureRecognizer.view.bounds.size.width;
+ BOOL isRTL = _controller.view.semanticContentAttribute == UISemanticContentAttributeForceRightToLeft;
+ if (isRTL) {
+ translation = -translation;
+ velocity = -velocity;
+ }
}
float transitionProgress = (translation / distance);
diff --git a/ios/RNSScreenStackAnimator.m b/ios/RNSScreenStackAnimator.m
index 103181919c..680cdc9d44 100644
--- a/ios/RNSScreenStackAnimator.m
+++ b/ios/RNSScreenStackAnimator.m
@@ -190,14 +190,30 @@ - (void)animateSlideFromBottomWithTransitionContext:(id {
const { options, render: renderScene } = descriptors[route.key];
const {
- customAnimationOnSwipe,
- fullScreenSwipeEnabled,
gestureEnabled,
headerShown,
homeIndicatorHidden,
@@ -159,16 +157,38 @@ const RouteView = ({
navigationBarHidden,
replaceAnimation = 'pop',
screenOrientation,
- stackAnimation,
statusBarAnimation,
statusBarColor,
statusBarHidden,
statusBarStyle,
statusBarTranslucent,
+ swipeDirection = 'horizontal',
transitionDuration,
} = options;
- let { stackPresentation = 'push' } = options;
+ let {
+ customAnimationOnSwipe,
+ fullScreenSwipeEnabled,
+ stackAnimation,
+ stackPresentation = 'push',
+ } = options;
+
+ if (swipeDirection === 'vertical') {
+ // for `vertical` direction to work, we need to set `fullScreenSwipeEnabled` to `true`
+ // so the screen can be dismissed from any point on screen.
+ // `customAnimationOnSwipe` needs to be set to `true` so the `stackAnimation` set by user can be used,
+ // otherwise `simple_push` will be used.
+ // Also, the default animation for this direction seems to be `slide_from_bottom`.
+ if (fullScreenSwipeEnabled === undefined) {
+ fullScreenSwipeEnabled = true;
+ }
+ if (customAnimationOnSwipe === undefined) {
+ customAnimationOnSwipe = true;
+ }
+ if (stackAnimation === undefined) {
+ stackAnimation = 'slide_from_bottom';
+ }
+ }
if (index === 0) {
// first screen should always be treated as `push`, it resolves problems with no header animation
@@ -212,6 +232,7 @@ const RouteView = ({
statusBarHidden={statusBarHidden}
statusBarStyle={statusBarStyle}
statusBarTranslucent={statusBarTranslucent}
+ swipeDirection={swipeDirection}
transitionDuration={transitionDuration}
onHeaderBackButtonClicked={() => {
navigation.dispatch({
diff --git a/src/types.tsx b/src/types.tsx
index 09e6f5ab99..fa12e038c2 100644
--- a/src/types.tsx
+++ b/src/types.tsx
@@ -47,6 +47,7 @@ export type BlurEffectTypes =
| 'systemThickMaterialDark'
| 'systemChromeMaterialDark';
export type ScreenReplaceTypes = 'push' | 'pop';
+export type SwipeDirectionTypes = 'vertical' | 'horizontal';
export type ScreenOrientationTypes =
| 'default'
| 'all'
@@ -232,6 +233,16 @@ export interface ScreenProps extends ViewProps {
* @platform android
*/
statusBarTranslucent?: boolean;
+ /**
+ * Sets the direction in which you should swipe to dismiss the screen.
+ * When using `vertical` option, options `fullScreenSwipeEnabled: true`, `customAnimationOnSwipe: true` and `stackAnimation: 'slide_from_bottom'` are set by default.
+ * The following values are supported:
+ * - `vertical` – dismiss screen vertically
+ * - `horizontal` – dismiss screen horizontally (default)
+ *
+ * @platform ios
+ */
+ swipeDirection?: SwipeDirectionTypes;
/**
* Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `350`.
* The duration of `default` and `flip` transitions isn't customizable.