YanYuanFE/react-native-signature-canvas

PanResponder with SignatureScreen not working inside ScrollView

talhauzair-28 opened this issue · 3 comments

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.

Waiting for a response.

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。

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