Skip to content

Commit

Permalink
fix: Swipe down to dismiss screen with ScrollView on iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphaël Blanchet committed Sep 26, 2023
1 parent a87885b commit c483533
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 0 deletions.
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 Test1884 from './src/Test1884';

enableFreeze(true);

Expand Down
69 changes: 69 additions & 0 deletions TestsExample/src/Test1884.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import * as React from 'react';
import { Button, View, Text, ScrollView } from 'react-native';
import { NavigationContainer, ParamListBase } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { NativeStackNavigationProp } from 'react-native-screens/native-stack';

const Stack = createNativeStackNavigator();

type NavProp = {
navigation: NativeStackNavigationProp<ParamListBase>;
};

export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="home"
component={Home}
options={{ headerTitle: 'Home' }}
/>
<Stack.Screen
name="screenA"
component={ScreenA}
options={{ headerShown: false, animation: 'slide_from_bottom', fullScreenGestureEnabled: true, gestureDirection: 'vertical' }}
/>
<Stack.Screen
name="screenB"
component={ScreenB}
options={{ headerShown: false, animation: 'slide_from_bottom' }}
/>
</Stack.Navigator>
</NavigationContainer>
);
}

const Home = ({ navigation }: NavProp) => (
<View style={{ flex: 1, alignItems: 'center', paddingTop: 50 }}>
<Text>Home</Text>
<Button
onPress={() => navigation.navigate('screenA')}
title="Go to dismissable screen"
/>
<Button
onPress={() => navigation.navigate('screenB')}
title="Go to undismissable screen"
/>
</View>
);

const ScreenA = ({ }: NavProp) => (
<ScrollView contentContainerStyle={{ flex: 1 }}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Dismissable screen</Text>
</View>
</ScrollView>
);

const ScreenB = ({ navigation }: NavProp) => (
<ScrollView contentContainerStyle={{ flex: 1 }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Undismissable screen</Text>
<Button
onPress={() => navigation.goBack()}
title="Back"
/>
</View>
</ScrollView>
);
14 changes: 14 additions & 0 deletions ios/RNSScreenStack.mm
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,20 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
BOOL isBackGesture = [panGestureRecognizer translationInView:panGestureRecognizer.view].x > 0 &&
_controller.viewControllers.count > 1;

// Detects if we are currently at the top of the ScrollView and if the gesture is a swipe down.
// We consider this only if the scroll is more vertical than horizontal
UIScrollView *scrollView = (UIScrollView *)otherGestureRecognizer.view;
CGPoint translation = [panGestureRecognizer translationInView:panGestureRecognizer.view];
BOOL shouldDismissWithScrollDown =
abs(translation.y) > abs(translation.x) && translation.y > 0 && scrollView.contentOffset.y <= 0;
if (shouldDismissWithScrollDown) {
// If we detect a dismiss swipe, we cancel the other gesture recognizer and consider only the screen's one
// (To cancel a gesture, we need to disable and re-enable it)
[otherGestureRecognizer setEnabled:NO];
[otherGestureRecognizer setEnabled:YES];
return YES;
}

if (gestureRecognizer.state == UIGestureRecognizerStateBegan || isBackGesture) {
return NO;
}
Expand Down

0 comments on commit c483533

Please sign in to comment.