software-mansion / react-native-gesture-handler

Declarative API exposing platform native touch and gesture system to React Native.
https://docs.swmansion.com/react-native-gesture-handler/
MIT License
5.85k stars 954 forks source link

Combining Pan Gesture and Swipeable creates unexpected behavior #2862

Closed Athe81 closed 3 weeks ago

Athe81 commented 1 month ago

Description

I have a GestureDetector with a pan gesture wraped around a Swipeable element. After wrapping the GestureDetector around the Swipeable element, the Swipeable Element does not work as expected. Before it opened or closed after releasing the finger. Now it stucks in the position.

Steps to reproduce

  1. Create a Swipeable element
  2. Wrap it with a GestureDetector (gesture pan)
  3. Test it

Snack or a link to a repository

https://snack.expo.dev/@bigbart/bugreport-swipeable-list

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

None

Device model

No response

Acknowledgements

Yes

m-bert commented 3 weeks ago

Hi @Athe81! Why do you want to wrap Swipeable with GestureDetector? Swipeables already have gesture handlers under the hood. Moreover, they use old API of gesture handler along with Animated API, which is not supported by GestureDetector (as you can read in the docs).

Athe81 commented 3 weeks ago

Hi @m-bert!

Why do you want to wrap Swipeable with GestureDetector? Swipeables already have gesture handlers under the hood.

I want to give a haptic feedback to the user, if the Swipeable item is swiped more than a threshold. This is to signalize the user, that the action behind, will be executed if he releases his finger. I don't know how to do this without GestureDetector.

m-bert commented 3 weeks ago

I see. In that case you can try something like this:

const Item = ({ id, title }) => {
  const windowWidth = Dimensions.get('window').width;
  const threshold = windowWidth / 3;
  const hasReachedThreshold = useSharedValue(false);
  const panRef = useRef();

  const panGesture = Gesture.Pan()
    .onUpdate((e) => {
      hasReachedThreshold.value = Math.abs(e.translationX) >= threshold;
      console.log(hasReachedThreshold.value);
    })
    .withRef(panRef);

  const renderLeftActions = (progress, dragX) => {
    return <View style={styles.left} />;
  };

  const renderRightActions = (progress, dragX) => {
    return <View style={styles.right} />;
  };

  return (
    <GestureDetector gesture={panGesture}>
      <View>
        <Swipeable
          key={id}
          simultaneousHandlers={panRef}
          renderLeftActions={renderLeftActions}
          renderRightActions={renderRightActions}
          leftThreshold={threshold}
          rightThreshold={threshold}>
          <View style={styles.item}>
            <Text style={styles.text}>{title}</Text>
          </View>
        </Swipeable>
      </View>
    </GestureDetector>
  );
};

I've made a few changes in your code:

  1. I've wrapped Swipeable with View so that GestureDetector won't be using the same view as swipeable component
  2. I've added withRef so you can mark Pan as simultaneous with gesture handlers inside Swipeable

With these changes I was able to achieve what I believe is what you're trying to do. Let me know if it helps!

Athe81 commented 3 weeks ago

It helped a lot. It works perfect now. Thanks a lot. I didn't know the simultaneousHandlers