YanYuanFE / react-native-signature-canvas

:black_nib: React Native Signature Component based WebView Canvas for Android && IOS && expo
MIT License
413 stars 148 forks source link

PanResponder with SignatureScreen not working inside ScrollView #258

Open talhauzair-28 opened 1 year ago

talhauzair-28 commented 1 year ago

Objective: I am trying to use SignatureScreen from react-native-signature-canvas inside a ScrollView. But whenever I try to scroll up and down. ScrollView takes the gesture and moves the screen instead of focusing on SignatureScreen.

I found the solution of using PanResponder as a wrapper on SignatureScreen to avoid the parent view getting the gesture event.

Following is the Pandresponder code:

PanResponder.create({
      // Ask to be the responder:
      onStartShouldSetPanResponder: (evt, gestureState) => true,
      onStartShouldSetPanResponderCapture: (evt, gestureState) =>
        true,
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) =>
        true,

      onPanResponderGrant: (evt, gestureState) => {
        // The gesture has started. Show visual feedback so the user knows
        // what is happening!
        // gestureState.d{x,y} will be set to zero now
      },
      onPanResponderMove: (evt, gestureState) => {
        // The most recent move distance is gestureState.move{X,Y}
        // The accumulated gesture distance since becoming responder is
        // gestureState.d{x,y}
      },
      onPanResponderTerminationRequest: (evt, gestureState) =>
        true,
      onPanResponderRelease: (evt, gestureState) => {
        // The user has released all touches while this view is the
        // responder. This typically means a gesture has succeeded
      },
      onPanResponderTerminate: (evt, gestureState) => {
        // Another component has become the responder, so this gesture
        // should be cancelled
      },
      onShouldBlockNativeResponder: (evt, gestureState) => {
        // Returns whether this component should block native components from becoming the JS
        // responder. Returns true by default. Is currently only supported on android.
        return true;
      }
    })
  ).current;

Following is the render code:

      <ScrollView>
        <View
          style={styles.signatureContainerStyle}
          {...panResponder.panHandlers}
        >
          <SignatureScreen
            ref={ref}
            onOK={onSignatureUpdate}
            onEnd={handleOnEndCallback} // Mandatory to trigger onOk on its own
            descriptionText={labelText}
            backgroundColor={CORE_COLORS.components.signature.background}
            webStyle={canvasStyle}
            webviewContainerStyle={{ opacity: 0.99 }}
            onBegin={handleOnFocus}
            dataURL={initialValue}
          />
        </View>
      </ScrollView>

This solution was working perfectly on iOS as the ScrollView did not get any gesture while drawing on SignatureScreen. However, on Android, SignatureScreen was also not getting the gesture. While the expected behaviour on android was like iOS, that SignatureScreen gets gesture and not the ScrollView while drawing.

Here is what I found: onStartShouldSetPanResponderCapture and onMoveShouldSetPanResponderCapture should return false, if we want the child to listen to gestures.

and we should use event.stopPropagation() and set the onPanResponderTerminationRequest to return false to stop the gesture event to propagate to the parent view. So here is what my updated code looks like:

const panResponder = React.useRef(
    PanResponder.create({
      // Ask to be the responder:
      onStartShouldSetPanResponder: (evt, gestureState) => false,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => false,

      onPanResponderGrant: (evt, gestureState) => {
        // The gesture has started. Show visual feedback so the user knows
        // what is happening!
        // gestureState.d{x,y} will be set to zero now
      },
      onPanResponderMove: (evt, gestureState) => {
        // The most recent move distance is gestureState.move{X,Y}
        // The accumulated gesture distance since becoming responder is
        // gestureState.d{x,y}
      },
      onPanResponderTerminationRequest: (evt, gestureState) => false,
      onPanResponderRelease: (evt, gestureState) => {
        // The user has released all touches while this view is the
        // responder. This typically means a gesture has succeeded
      },
      onPanResponderTerminate: (evt, gestureState) => {
        // Another component has become the responder, so this gesture
        // should be cancelled
      },
      onShouldBlockNativeResponder: (evt, gestureState) => {
        // Returns whether this component should block native components from becoming the JS
        // responder. Returns true by default. Is currently only supported on android.
        return true;
      },
    })
  ).current;

But the issue is still the same, on Android phones, the child component is not getting the gesture. It only draws a single dot and that's it.

Any help would be appreciated.

talhauzair-28 commented 1 year ago

Waiting for a response.

YanYuanFE commented 1 year ago

Sorry, there has never been a better way to use SignatureScreen and ScrollView together. I'm not very familiar with react native, and I don't have much time to dig into this problem. You are very welcome to read our source code to find the cause of the problem, and welcome to send PR。

ajithlee-reactnative commented 2 months ago

just add this prop nestedScrollEnabled={false} - inside the SignatureScreen component