I created a native Modal
with using Gesture
from react-native-gesture-handler
so that the Modal can be closed by swiping down. On iOS, after closing the modal and switching to other screens a few times, the app freezes/closes or needs to be restarted. There is no error in the console, but there is a message in Xcode that the app was stopped.
Root App.tsx
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import RootNavigator from './navigation/RootNavigator/RootNavigator';
const App: FC = () => (
<ThemeProvider>
<LocaleProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaProvider>
<RootNavigator />
</SafeAreaProvider>
</GestureHandlerRootView>
</LocaleProvider>
</ThemeProvider>
);
export default App;
CustomModal.tsx
import { PropsWithChildren, useRef } from 'react';
import {
Modal,
Dimensions,
ModalProps,
View,
StyleProp,
ViewStyle,
Platform,
SafeAreaView,
Text
} from 'react-native';
import {
ScrollView,
GestureHandlerRootView,
GestureDetector,
Gesture,
GestureType,
} from 'react-native-gesture-handler';
import { runOnJS, useSharedValue, withSpring } from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useTheme } from '../../theme';
interface I Modal extends ModalProps {
onClose: () => void;
headerTitle?: string;
scrollEnabled?: boolean;
}
const { height: windowHeight } = Dimensions.get('window');
export function CustomModal({
children,
onClose,
headerTitle,
scrollEnabled,
...props
}: PropsWithChildren<IModal>) {
const { layout, gutters, colors, borders, fonts, navigationTheme } =
useTheme();
const translateY = useSharedValue(0);
const insets = useSafeAreaInsets();
const simultaneousRef = useRef<GestureType | undefined>(undefined);
const scrollViewRef = useRef<ScrollView | null>(null);
const panGesture = Gesture.Pan()
.onUpdate((event) => {
translateY.value = event.translationY;
})
.onEnd((event) => {
if (event.translationY > windowHeight / 4) {
runOnJS(onClose)();
} else {
translateY.value = withSpring(0);
}
})
.simultaneousWithExternalGesture(scrollViewRef)
.withRef(simultaneousRef);
const contentContainerStyle: ViewStyle[] = [
layout.flexGrow_1,
gutters.paddingHorizontal_16,
];
return (
<Modal
animationType="slide"
onRequestClose={onClose}
presentationStyle="pageSheet"
transparent={false}
{...props}
>
<GestureHandlerRootView style={[layout.flex_1]}>
<SafeAreaView style={[layout.flex_1]}>
<GestureDetector gesture={panGesture}>
<View
style={[
layout.fullHeight,
layout.fullWidth,
gutters.paddingTop_10,
]}
>
<View
style={[
layout.row,
layout.itemsCenter,
layout.justifyBetween,
gutters.paddingVertical_16,
]}
>
<View style={[layout.flex_1]}>
<Text
numberOfLines={1}
style={[fonts.center, fonts.size_18, fonts.lineHeight_22]}
>
{headerTitle}
</Text>
</View>
<View style={[layout.flex_05, gutters.marginLeft_4]} />
</View>
<ScrollView
ref={scrollViewRef}
contentContainerStyle={contentContainerStyle}
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="handled"
nestedScrollEnabled
scrollEnabled={scrollEnabled}
simultaneousHandlers={simultaneousRef}
>
{children}
</ScrollView>
</View>
</GestureDetector>
</SafeAreaView>
</GestureHandlerRootView>
</Modal>
);
}