react-native
react-native copied to clipboard
PanResponder with SignatureScreen not working.
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;
Waiting for a response.
@JoshuaGross