react-native icon indicating copy to clipboard operation
react-native copied to clipboard

PanResponder with SignatureScreen not working.

Open talhauzair-28 opened this issue 1 year ago • 1 comments

Description

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 onPanResponderTerminationRequeston 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, child component is not getting the gesture. It only draws a single dot and that's it.

Version

0.69.4

Output of npx react-native info

System: OS: macOS 12.2 CPU: (8) x64 Apple M1 Memory: 22.76 MB / 8.00 GB Shell: 5.8 - /bin/zsh Binaries: Node: 16.4.1 - ~/.nvm/versions/node/v16.4.1/bin/node Yarn: 1.22.19 - ~/.yarn/bin/yarn npm: 8.16.0 - ~/.nvm/versions/node/v16.4.1/bin/npm Watchman: 2022.07.04.00 - /usr/local/bin/watchman Managers: CocoaPods: 1.10.2 - /Users/talhauzair/.rvm/gems/ruby-2.7.1/bin/pod SDKs: iOS SDK: Platforms: DriverKit 21.4, iOS 15.5, macOS 12.3, tvOS 15.4, watchOS 8.5 Android SDK: API Levels: 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 9 Build Tools: 20.0.0, 25.0.2, 28.0.3, 29.0.2, 30.0.2, 30.0.3, 31.0.0, 31.0.0, 31.0.0, 31.0.0, 33.0.0, 33.0.0, 33.0.0, 33.0.0 System Images: android-22 | Google APIs Intel x86 Atom, android-28 | Google Play Intel x86 Atom, android-30 | Intel x86 Atom_64, android-30 | Google APIs ARM 64 v8a, android-30 | Google APIs Intel x86 Atom, android-30 | Google APIs Intel x86 Atom_64, android-30 | Google Play ARM 64 v8a, android-30 | Google Play Intel x86 Atom, android-30 | Google Play Intel x86 Atom_64 Android NDK: Not Found IDEs: Android Studio: 2021.2 AI-212.5712.43.2112.8815526 Xcode: 13.4.1/13F100 - /usr/bin/xcodebuild Languages: Java: 11.0.15 - /usr/bin/javac npmPackages: @react-native-community/cli: Not Found react: 18.0.0 => 18.0.0 react-native: 0.69.4 => 0.69.4 react-native-macos: Not Found npmGlobalPackages: react-native: Not Found

Steps to reproduce

install following packages:

  • "react-native-signature-canvas"
  • "react-native-web"

Place the SignatureScreen component inside ScrollView and use a wrapper View with PanResponder. Test on Android and iOS devices.

Snack, code example, screenshot, or link to a repository

  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;

talhauzair-28 avatar Aug 22 '22 07:08 talhauzair-28

Waiting for a response.

talhauzair-28 avatar Aug 23 '22 10:08 talhauzair-28

@JoshuaGross

talhauzair-28 avatar Aug 29 '22 05:08 talhauzair-28