Closed spthomas5 closed 7 months ago
Hey! 👋
The issue doesn't seem to contain a minimal reproduction.
Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?
As of now I seem to have gotten around this issue by wrapping all instances of GestureDetector with GestureHandlerRootView, but I'm not sure if this is the intended solution.
<GestureHandlerRootView style={{ flex: 1 }}>
<GestureDetector gesture={Gesture.Exclusive(doubleTap)}>
<Image
source={item.source}
style={{
height: windowHeight / 1.8,
width: windowWidth,
marginTop: "5%",
alignSelf: "center",
}}
transition={1000}
contentFit="cover"
cachePolicy={"disk"}
/>
</GestureDetector>
</GestureHandlerRootView>
Hi @spthomas5! I can't see it directly in your snippets, but our guess is that you are using GestureDetector
inside modal
. Since they represent another view hierarchy, you should also wrap modal's children into GestureHandlerRootView
Also, you don't have to pass {flex: 1}
into GestureHandlerRootView
since this is already done by us (added in #2757).
Edit:
PR with default {flex: 1}
was released yesterday, so it is possible that it might not be available in your application yet.
@m-bert Thank you for the reply. Thanks to your help, I no longer receive the error. However, now my modal (BottomSheetModal from gorhom) no longer works as it did before. I understand if you are not familiar with the library, but if you want to take a look, I have more details here. https://github.com/gorhom/react-native-bottom-sheet/issues/1779 If not, no worries. I appreciate your help!
I'm glad that you don't receive errors anymore. I'm not that familiar with react-native-bottom-sheet
so probably I won't be able to help. As I see you've already created an issue in their repository, therefore I'll close this one. If you find out that the problem still lies in Gesture Handler, feel free to open new issue.
Why use GestureHandlerRootView? It's not user-friendly. For instance, when I create a custom component and I want to inform the user that this component needs to be wrapped in a GestureHandlerRootView at the top level of the element tree. If I place the root component inside the custom component, it could potentially mess up the user's layout hierarchy and styles.
Why use GestureHandlerRootView? It's not user-friendly.
In order to work properly, our gesture system requires root view on Android. It was there from the beginning and, while we understand that this API may not be the best, it is necessary. We try to make it more user-friendly with PRs like adding {flex: 1;} by default. If you have any other suggestions we will be glad to receive them.
when I create a custom component and I want to inform the user that this component needs to be wrapped in a GestureHandlerRootView
I don't understand why you should tell your users that they need to use our root view. Are you maintaining some sort of library?
If I place the root component inside the custom component, it could potentially mess up the user's layout hierarchy and styles.
Same here. Why it would mess up user's hierarchy? Root view is just View
that allows gesture handler to work - you can style it as you would style normal view.
Why use GestureHandlerRootView? It's not user-friendly.
In order to work properly, our gesture system requires root view on Android. It was there from the beginning and, while we understand that this API may not be the best, it is necessary. We try to make it more user-friendly with PRs like adding {flex: 1;} by default. If you have any other suggestions we will be glad to receive them.
when I create a custom component and I want to inform the user that this component needs to be wrapped in a GestureHandlerRootView
I don't understand why you should tell your users that they need to use our root view. Are you maintaining some sort of library?
If I place the root component inside the custom component, it could potentially mess up the user's layout hierarchy and styles.
Same here. Why it would mess up user's hierarchy? Root view is just
View
that allows gesture handler to work - you can style it as you would style normal view.
Thanks for your reply! Yesterday, I was working on a component called Movable. It is a container that allows anything inside it to be dragged. However, I discovered that if I don't place a fullscreen GestureHandlerRootView on top of it, the Movable component won't function correctly. So, I attempted to add a GestureHandlerRootView with position: absolute styles. Unfortunately, it ended up blocking any other components from being able to be touched underneath it. So. I come out this question...
import React, { PropsWithChildren } from 'react'
import { LayoutChangeEvent, Dimensions, StyleSheet } from 'react-native'
import isEqual from 'lodash.isequal'
import Animated, {
clamp,
runOnJS,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated'
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
import snapPoint from './utils/snapPoint'
const defaultProps = {
padding: 20,
initPosition: { left: 0, top: 0 },
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
},
})
const _safeAreaBottom = 100
const _safeAreaTop = 0
export type Props = {
initPosition: { left?: number; top?: number; bottom?: number; right?: number }
padding?: number
paddingTop?: number
paddingBottom?: number
paddingRight?: number
paddingLeft?: number
safeAreaBottom?: number
safeAreaTop?: number
}
const windowWidth = Dimensions.get('screen').width
const windowHeight = Dimensions.get('screen').height
const Movable: React.FC<PropsWithChildren<Props>> = (props) => {
const {
padding = defaultProps.padding,
initPosition = defaultProps.initPosition,
safeAreaBottom = _safeAreaBottom,
safeAreaTop = _safeAreaTop,
} = props
const paddingTop = props.paddingTop || padding
const paddingBottom = props.paddingBottom || padding
const paddingRight = props.paddingRight || padding
const paddingLeft = props.paddingLeft || padding
const contentWidth = useSharedValue<number>(0)
const contentHeight = useSharedValue<number>(0)
const initX =
paddingRight + (initPosition.left || defaultProps.initPosition.left)
const initY =
paddingBottom + (initPosition.top || defaultProps.initPosition.top)
const visible = useSharedValue(0)
const offsetX = useSharedValue(initX)
const offsetY = useSharedValue(initY)
const transX = useSharedValue(initX)
const transY = useSharedValue(initY)
const handleLayout = (event: LayoutChangeEvent) => {
const { width, height } = event.nativeEvent.layout
contentWidth.value = width
contentHeight.value = height
visible.value = 1
transX.value = offsetX.value = windowWidth - width - paddingRight
transY.value = offsetY.value =
windowHeight - height - paddingBottom - safeAreaBottom
}
const onEnd = (event: any, currentX: number) => {
const toX = snapPoint(currentX, event.velocityX, [
0 + paddingLeft,
windowWidth - contentWidth.value - paddingRight,
])
transX.value = withTiming(toX, { duration: 200 }, () => {
offsetX.value = transX.value
offsetY.value = transY.value
})
}
const pan = Gesture.Pan()
.onStart(() => {
transX.value = offsetX.value
transY.value = offsetY.value
})
.onUpdate((event) => {
transX.value = offsetX.value + event.translationX
transY.value = clamp(
offsetY.value + event.translationY + safeAreaTop,
0 + paddingTop,
windowHeight - contentHeight.value - paddingBottom - safeAreaBottom,
)
})
.onFinalize((event) => {
runOnJS(onEnd)(event, transX.value)
})
const transformStyles = useAnimatedStyle(() => {
return {
opacity: visible.value,
transform: [
{
translateX: transX.value,
},
{
translateY: transY.value,
},
],
}
})
return (
<GestureDetector gesture={pan}>
<Animated.View
onLayout={handleLayout}
style={[styles.container, transformStyles]}>
{props.children}
</Animated.View>
</GestureDetector>
)
}
export default React.memo(Movable, isEqual)
I've played a bit with your example and I believe that it works ok. First of all, I had to make some modifications to run it (for example I had no access to snapPoint
function). You can check my code here:
This is how it works:
Note that you can also move GestureHandlerRootView
into your Movable
component. So instead of doing:
<GestureHandlerRootView>
<Movable initPosition={{ left: 0, top: 0 }}>
<View style={{ width: 100, height: 100, backgroundColor: 'lime' }} />
<View style={{ width: 100, height: 100, backgroundColor: 'crimson' }} />
</Movable>
</GestureHandlerRootView>
You can do:
<GestureHandlerRootView>
<GestureDetector gesture={pan}>
<Animated.View
onLayout={handleLayout}
style={[styles.container, transformStyles]}>
{props.children}
</Animated.View>
</GestureDetector>
</GestureHandlerRootView>
which would be better if you export your component to other users.
I think the problem comes from the fact that you try to use {position: absolute;}
on GestureHandlerRootView
. Gestures work only on area occupied by GestureHandlerRootView
, therefore if some of its children are outside of its bounds, their gestures won't work.
I've also decided to add this information to our docs since it wasn't mentioned there and I believe it could be a bit confusing 😅
Description
I previously was not getting this error, but I recently updated many of my libraries (this includes Expo SDK 50) and now when I go on my dev build, I get this error wherever I use GestureDetector. This includes the camera, where it handles double tapping to flip the camera. It also includes all screens where posts are displayed, where it handles double tapping to like. However, my TestFlight app works fine, including the double tap gestures that I use. I'm not sure why I get this error in the first place, as I already have GestureHandlerRootView around my entry point.
Steps to reproduce
This is my index.js
And this is an example of my use of GestureDetector
Snack or a link to a repository
None
Gesture Handler version
2.14.0
React Native version
0.73.6
Platforms
iOS
JavaScript runtime
Hermes
Workflow
Expo managed workflow
Architecture
None
Build type
None
Device
Real device
Device model
iPhone 14 Pro, iOS 17.1.2
Acknowledgements
Yes