Pan gesture doesn't activate for some time after scrolling the parent scrollable
Description
Pan gesture works usually fine inside a ScrollView or other component except some cases, when I try to drag an item a while after scrolling the scrollable parent container. I would expect the Pan gesture to activate in such a case as well but instead, I have to wait for a noticeable amount of time until it works again. Before this time elapses, the Pan gesture calls just the onTouchesDown and onBegin callbacks and no other callback is called later on (if the gesture cannot be handled, I would expect to just receive the onFinalize callback call to handle such a case).
I noticed the problem only on iOS (simulator and real device).
Example recording
https://github.com/user-attachments/assets/87f6549c-f1e4-411d-a6ae-bb0b835f43b8
Steps to reproduce
- Clone this repo and build the app from the
mainbranch - See the issue on iOS
Snack or a link to a repository
https://github.com/MatiPl01/gesture-handler-issues
Gesture Handler version
2.18.1
React Native version
0.74.5
Platforms
iOS
JavaScript runtime
Hermes
Workflow
React Native (without Expo)
Architecture
New and Old
Build type
Debug mode
Device
iOS simulator, real device
Device model
tested on iPhone 15 Pro (real device, simulator)
Acknowledgements
Yes
Hi! I've looked into your repro. The thing is, on iOS Gesture Handler uses native recognizers and in case of ScrollView it has about ±150ms window in which pan gesture will be recognized as scroll. I don't think there's much that we can do about it 😞 (cc @j-piasecki).
Before this time elapses, the Pan gesture calls just the onTouchesDown and onBegin callbacks and no other callback is called later on
That's strange, even on the video that you've attached I can see that you get both, onTouchesCancelled and onFinalize callbacks. Moreover, onFinalize has second parameter, representing if gesture ended successfully, or not. In my case I get false:
https://github.com/user-attachments/assets/83ec05b3-6180-4bb3-964a-ccd916639813
As a side note, you can simplify your component like this:
<ScrollView contentContainerStyle={styles.content}>
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
</ScrollView>
Hi! I've looked into your repro. The thing is, on iOS Gesture Handler uses native recognizers and in case of
ScrollViewit has about±150mswindow in which pan gesture will be recognized as scroll. I don't think there's much that we can do about it 😞 (cc @j-piasecki).Before this time elapses, the Pan gesture calls just the onTouchesDown and onBegin callbacks and no other callback is called later on
That's strange, even on the video that you've attached I can see that you get both,
onTouchesCancelledandonFinalizecallbacks. Moreover,onFinalizehas second parameter, representing if gesture ended successfully, or not. In my case I getfalse:Nagranie.z.ekranu.2024-08-16.o.11.46.53.mov As a side note, you can simplify your component like this:
<ScrollView contentContainerStyle={styles.content}> <GestureDetector gesture={panGesture}> <Animated.View style={[styles.box, animatedStyle]} /> </GestureDetector> </ScrollView>
onFinalize and onTouchesCancelled are fired only after you release the finger but (at least to me) they should be called before releasing the finger.
See how this can be problematic on the following recording. The ScrollView recognizes a gesture but the pan gesture doesn't call any of callbacks when the ScrollView starts handling pan:
https://github.com/user-attachments/assets/3ab0ce87-67c0-416b-8232-ed6c760eb3ce
I got it working by using the Manual gesture which I can cancel manually when I know that the pan gesture would not be handled but still it'd better if pan gesture could be handled after scroll. I understand that this might be not possible on iOS, though, so we can close this issue if nothing more can be done.
As a side note, you can simplify your component like this
Yeah, I know I can simplify code but this is just a repro and I copy-pasted code from my other project (where I need 2 separate components), did some cleanup and didn't care about the simplest implementation.
(...) but still it'd better if pan gesture could be handled after scroll.
I don't think it is something that we will be able to achieve right now 😞 As you can see, there's no touchesMoved nor onChange callback, so it might be hard to manipulate scroll without access to them.
I understand that this might be not possible on iOS, though, so we can close this issue if nothing more can be done.
While I can't see good solution to this problem, maybe there is one. Please leave it open until @j-piasecki responds - then we can either close it, or try something else 😅
but still it'd better if pan gesture could be handled after scroll.
The problem here is that the scroll is still technically active (if you set bounces={false} on the ScrollView, the issue is gone. The weird thing here is that UIKit calls onTouchesBegan, where we trigger onTouchesDown and onBegin callbacks, but it doesn't do anything else. From what I was able to find, it doesn't in any way inform us that the gesture got effectively canceled.
I think it's because we're in Possible state, so it assumes no actions were done. If that's true, then it's a weird side effect of mapping UIKit states to RNGH states.
As for your use case, wouldn't moving the highlighting logic to onStart work, since it's not triggered while the scroll is still active? You could try combining it with activateAfterLongPress.
As for the issue, I think we can keep it open as it should be fixable. The relevant info must exist somewhere since the pan recognizer receives down event but doesn't receive any subsequent move events. It's just a matter of finding it, or figuring out that Apple in all it's generosity decided that we mere developers aren't worthy enough to access it.
but still it'd better if pan gesture could be handled after scroll.
The problem here is that the scroll is still technically active (if you set
bounces={false}on the ScrollView, the issue is gone. The weird thing here is that UIKit callsonTouchesBegan, where we triggeronTouchesDownandonBegincallbacks, but it doesn't do anything else. From what I was able to find, it doesn't in any way inform us that the gesture got effectively canceled.I think it's because we're in
Possiblestate, so it assumes no actions were done. If that's true, then it's a weird side effect of mapping UIKit states to RNGH states.As for your use case, wouldn't moving the highlighting logic to
onStartwork, since it's not triggered while the scroll is still active? You could try combining it withactivateAfterLongPress.As for the issue, I think we can keep it open as it should be fixable. The relevant info must exist somewhere since the pan recognizer receives
downevent but doesn't receive any subsequentmoveevents. It's just a matter of finding it, or figuring out that Apple in all it's generosity decided that we mere developers aren't worthy enough to access it.
I wanted to start the item scale change animation when it is touched with a slight delay (I used withDelay inside the onTouchesDown callback to start the animation after a slight delay and cancelled the animation if onTouchesUp or onTouchesCancelled was called).
I used the Pan gesture together with the activateAfterLongPress function but I wanted to keep the scale animation separate. When the Pan gesture was activated, the onUpdate function started being called with translation offsets (onUpdate starts being called right after onStart, so I cannot move my logic to onStart as the scale animation must begin some time before).
I would try to experiment with activateAfterLongPress a bit more. Maybe I can just activate the Pan gesture earlier, move the scale animation to onStart and ignore updates from onUpdate as long as the scale animation is not finished.
Thank you @m-bert and @j-piasecki for your explanations!