diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt index 62cf547c19..981eff79a6 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt @@ -216,6 +216,11 @@ open class ScreenViewManager : value: Boolean, ) = Unit + override fun setFullScreenSwipeShadowEnabled( + view: Screen?, + value: Boolean, + ) = Unit + override fun setTransitionDuration( view: Screen?, value: Int, diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java index 74fc87dbd5..02540fdb1e 100644 --- a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java @@ -44,6 +44,9 @@ public void setProperty(T view, String propName, @Nullable Object value) { case "fullScreenSwipeEnabled": mViewManager.setFullScreenSwipeEnabled(view, value == null ? false : (boolean) value); break; + case "fullScreenSwipeShadowEnabled": + mViewManager.setFullScreenSwipeShadowEnabled(view, value == null ? false : (boolean) value); + break; case "homeIndicatorHidden": mViewManager.setHomeIndicatorHidden(view, value == null ? false : (boolean) value); break; diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java index 08f148eb7d..832a65584f 100644 --- a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java @@ -21,6 +21,7 @@ public interface RNSScreenManagerInterface { void setSheetExpandsWhenScrolledToEdge(T view, boolean value); void setCustomAnimationOnSwipe(T view, boolean value); void setFullScreenSwipeEnabled(T view, boolean value); + void setFullScreenSwipeShadowEnabled(T view, boolean value); void setHomeIndicatorHidden(T view, boolean value); void setPreventNativeDismiss(T view, boolean value); void setGestureEnabled(T view, boolean value); diff --git a/apps/src/tests/Test2227.tsx b/apps/src/tests/Test2227.tsx index f7e60c5fd5..5d7d20aaf6 100644 --- a/apps/src/tests/Test2227.tsx +++ b/apps/src/tests/Test2227.tsx @@ -48,6 +48,7 @@ export default function App() { diff --git a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md index 8ded1b65fb..be327f4cb9 100644 --- a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md +++ b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md @@ -50,6 +50,12 @@ Defaults to `false`. When `enableFreeze()` is run at the top of the application Boolean indicating whether the swipe gesture should work on whole screen. Swiping with this option results in the same transition animation as `simple_push` by default. It can be changed to other custom animations with `customAnimationOnSwipe` prop, but default iOS swipe animation is not achievable due to usage of custom recognizer. Defaults to `false`. +### `fullScreenSwipeShadowEnabled` (iOS only) + +Boolean indicating whether the full screen dismiss gesture has shadow under view during transition. The gesture uses custom transition and thus +doesn't have a shadow by default. When enabled, a custom shadow view is added during the transition which tries to mimic the +default iOS shadow. Defaults to `false`. + ### `gestureEnabled` (iOS only) When set to `false` the back swipe gesture will be disabled. The default value is `true`. diff --git a/ios/RNSScreen.h b/ios/RNSScreen.h index bbdedcdb9a..2c21edade6 100644 --- a/ios/RNSScreen.h +++ b/ios/RNSScreen.h @@ -56,6 +56,7 @@ namespace react = facebook::react; #endif @property (nonatomic) BOOL fullScreenSwipeEnabled; +@property (nonatomic) BOOL fullScreenSwipeShadowEnabled; @property (nonatomic) BOOL gestureEnabled; @property (nonatomic) BOOL hasStatusBarHiddenSet; @property (nonatomic) BOOL hasStatusBarStyleSet; diff --git a/ios/RNSScreen.mm b/ios/RNSScreen.mm index 2da6d9ad7e..932ec9d8db 100644 --- a/ios/RNSScreen.mm +++ b/ios/RNSScreen.mm @@ -703,6 +703,8 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props:: [self setFullScreenSwipeEnabled:newScreenProps.fullScreenSwipeEnabled]; + [self setFullScreenSwipeShadowEnabled:newScreenProps.fullScreenSwipeShadowEnabled]; + [self setGestureEnabled:newScreenProps.gestureEnabled]; [self setTransitionDuration:[NSNumber numberWithInt:newScreenProps.transitionDuration]]; @@ -1427,6 +1429,7 @@ @implementation RNSScreenManager RCT_REMAP_VIEW_PROPERTY(activityState, activityStateOrNil, NSNumber) RCT_EXPORT_VIEW_PROPERTY(customAnimationOnSwipe, BOOL); RCT_EXPORT_VIEW_PROPERTY(fullScreenSwipeEnabled, BOOL); +RCT_EXPORT_VIEW_PROPERTY(fullScreenSwipeShadowEnabled, BOOL); RCT_EXPORT_VIEW_PROPERTY(gestureEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(gestureResponseDistance, NSDictionary) RCT_EXPORT_VIEW_PROPERTY(hideKeyboardOnSwipe, BOOL) diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index e31d20d4ea..abb2cf69fd 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -9,6 +9,9 @@ static const float RNSSlideCloseTransitionDurationProportion = 0.25 / 0.35; static const float RNSFadeCloseTransitionDurationProportion = 0.15 / 0.35; static const float RNSFadeCloseDelayTransitionDurationProportion = 0.1 / 0.35; +// same value is used in other projects using similar approach for transistions +// and it looks the most similar to the value used by Apple +static constexpr float RNSShadowViewMaxAlpha = 0.1; @implementation RNSScreenStackAnimator { UINavigationControllerOperation _operation; @@ -71,17 +74,22 @@ - (void)animateTransition:(id)transitionCo // we are swiping with full width gesture if (screen.customAnimationOnSwipe) { [self animateTransitionWithStackAnimation:screen.stackAnimation + shadowEnabled:screen.fullScreenSwipeShadowEnabled transitionContext:transitionContext toVC:toViewController fromVC:fromViewController]; } else { // we have to provide an animation when swiping, otherwise the screen will be popped immediately, // so in case of no custom animation on swipe set, we provide the one closest to the default - [self animateSimplePushWithTransitionContext:transitionContext toVC:toViewController fromVC:fromViewController]; + [self animateSimplePushWithShadowEnabled:screen.fullScreenSwipeShadowEnabled + transitionContext:transitionContext + toVC:toViewController + fromVC:fromViewController]; } } else { // we are going forward or provided custom animation on swipe or clicked native header back button [self animateTransitionWithStackAnimation:screen.stackAnimation + shadowEnabled:screen.fullScreenSwipeShadowEnabled transitionContext:transitionContext toVC:toViewController fromVC:fromViewController]; @@ -89,9 +97,10 @@ - (void)animateTransition:(id)transitionCo } } -- (void)animateSimplePushWithTransitionContext:(id)transitionContext - toVC:(UIViewController *)toViewController - fromVC:(UIViewController *)fromViewController +- (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled + transitionContext:(id)transitionContext + toVC:(UIViewController *)toViewController + fromVC:(UIViewController *)fromViewController { float containerWidth = transitionContext.containerView.bounds.size.width; float belowViewWidth = containerWidth * 0.3; @@ -105,15 +114,32 @@ - (void)animateSimplePushWithTransitionContext:(id)transitionContext toVC:(UIViewController *)toVC fromVC:(UIViewController *)fromVC { if (animation == RNSScreenStackAnimationSimplePush) { - [self animateSimplePushWithTransitionContext:transitionContext toVC:toVC fromVC:fromVC]; + [self animateSimplePushWithShadowEnabled:shadowEnabled transitionContext:transitionContext toVC:toVC fromVC:fromVC]; return; } else if (animation == RNSScreenStackAnimationSlideFromLeft) { [self animateSlideFromLeftWithTransitionContext:transitionContext toVC:toVC fromVC:fromVC]; @@ -402,7 +439,7 @@ - (void)animateTransitionWithStackAnimation:(RNSScreenStackAnimation)animation return; } // simple_push is the default custom animation - [self animateSimplePushWithTransitionContext:transitionContext toVC:toVC fromVC:fromVC]; + [self animateSimplePushWithShadowEnabled:shadowEnabled transitionContext:transitionContext toVC:toVC fromVC:fromVC]; } @end diff --git a/native-stack/README.md b/native-stack/README.md index 302832edea..ce96e8efed 100644 --- a/native-stack/README.md +++ b/native-stack/README.md @@ -91,6 +91,12 @@ Enum value indicating display mode of **default** back button. It works on iOS > Boolean indicating whether the swipe gesture should work on whole screen. Swiping with this option results in the same transition animation as `simple_push` by default. It can be changed to other custom animations with `customAnimationOnSwipe` prop, but default iOS swipe animation is not achievable due to usage of custom recognizer. Defaults to `false`. +### `fullScreenSwipeShadowEnabled` (iOS only) + +Boolean indicating whether the full screen dismiss gesture has shadow under view during transition. The gesture uses custom transition and thus +doesn't have a shadow by default. When enabled, a custom shadow view is added during the transition which tries to mimic the +default iOS shadow. Defaults to `false`. + #### `gestureEnabled` (iOS only) Whether you can use gestures to dismiss this screen. Defaults to `true`. diff --git a/react-navigation b/react-navigation index 77911917bb..fdbfe7608c 160000 --- a/react-navigation +++ b/react-navigation @@ -1 +1 @@ -Subproject commit 77911917bb4d1137be267a321fb8a20bba772a06 +Subproject commit fdbfe7608c3c9ba4c2c3c95dcc5a6501ec97a056 diff --git a/src/fabric/ModalScreenNativeComponent.ts b/src/fabric/ModalScreenNativeComponent.ts index 18efd68a16..bb59c4c68d 100644 --- a/src/fabric/ModalScreenNativeComponent.ts +++ b/src/fabric/ModalScreenNativeComponent.ts @@ -77,6 +77,7 @@ export interface NativeProps extends ViewProps { sheetExpandsWhenScrolledToEdge?: WithDefault; customAnimationOnSwipe?: boolean; fullScreenSwipeEnabled?: boolean; + fullScreenSwipeShadowEnabled?: boolean; homeIndicatorHidden?: boolean; preventNativeDismiss?: boolean; gestureEnabled?: WithDefault; diff --git a/src/fabric/ScreenNativeComponent.ts b/src/fabric/ScreenNativeComponent.ts index 439cd80308..71c2251827 100644 --- a/src/fabric/ScreenNativeComponent.ts +++ b/src/fabric/ScreenNativeComponent.ts @@ -77,6 +77,7 @@ export interface NativeProps extends ViewProps { sheetExpandsWhenScrolledToEdge?: WithDefault; customAnimationOnSwipe?: boolean; fullScreenSwipeEnabled?: boolean; + fullScreenSwipeShadowEnabled?: boolean; homeIndicatorHidden?: boolean; preventNativeDismiss?: boolean; gestureEnabled?: WithDefault; diff --git a/src/native-stack/types.tsx b/src/native-stack/types.tsx index 88746a81fa..5ad3184ce5 100644 --- a/src/native-stack/types.tsx +++ b/src/native-stack/types.tsx @@ -134,6 +134,16 @@ export type NativeStackNavigationOptions = { * @platform ios */ fullScreenSwipeEnabled?: boolean; + /** + * Whether the full screen dismiss gesture has shadow under view during transition. The gesture uses custom transition and thus + * doesn't have a shadow by default. When enabled, a custom shadow view is added during the transition which tries to mimic the + * default iOS shadow. Defaults to `false`. + * + * This does not affect the behavior of transitions that don't use gestures, enabled by `fullScreenGestureEnabled` prop. + * + * @platform ios + */ + fullScreenSwipeShadowEnabled?: boolean; /** * Whether you can use gestures to dismiss this screen. Defaults to `true`. * Only supported on iOS. diff --git a/src/native-stack/views/NativeStackView.tsx b/src/native-stack/views/NativeStackView.tsx index 626fb62a96..58f383a5f6 100644 --- a/src/native-stack/views/NativeStackView.tsx +++ b/src/native-stack/views/NativeStackView.tsx @@ -174,6 +174,7 @@ const RouteView = ({ }) => { const { options, render: renderScene } = descriptors[route.key]; const { + fullScreenSwipeShadowEnabled = false, gestureEnabled, headerShown, hideKeyboardOnSwipe, @@ -294,6 +295,7 @@ const RouteView = ({ customAnimationOnSwipe={customAnimationOnSwipe} freezeOnBlur={freezeOnBlur} fullScreenSwipeEnabled={fullScreenSwipeEnabled} + fullScreenSwipeShadowEnabled={fullScreenSwipeShadowEnabled} hideKeyboardOnSwipe={hideKeyboardOnSwipe} homeIndicatorHidden={homeIndicatorHidden} gestureEnabled={isAndroid ? false : gestureEnabled} diff --git a/src/types.tsx b/src/types.tsx index 5f8c6e5cb5..f83ad569bd 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -133,6 +133,16 @@ export interface ScreenProps extends ViewProps { * @platform ios */ fullScreenSwipeEnabled?: boolean; + /** + * Whether the full screen dismiss gesture has shadow under view during transition. The gesture uses custom transition and thus + * doesn't have a shadow by default. When enabled, a custom shadow view is added during the transition which tries to mimic the + * default iOS shadow. Defaults to `false`. + * + * This does not affect the behavior of transitions that don't use gestures, enabled by `fullScreenGestureEnabled` prop. + * + * @platform ios + */ + fullScreenSwipeShadowEnabled?: boolean; /** * Whether you can use gestures to dismiss this screen. Defaults to `true`. *