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, the child component is not getting the gesture. It only draws a single dot and that's it.
Following are the versions:
- "react-native": "0.69.4",
- "react-native-signature-canvas": "^4.3.0",
- "react-native-webview": "11.23.0",