Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RN Touch events are canceled even when {gestureEnabled: false} on iOS when using native stack navigation #1022

Closed
owaiswiz opened this issue Jul 21, 2021 · 2 comments · Fixed by #1032

Comments

@owaiswiz
Copy link

owaiswiz commented Jul 21, 2021

Description

For example, if you have a component at the edge like a slider, it's touches are canceled if you start sliding from the left edge. That might be alright if the screen has gesture enabled, but I don't think it's correct to do that when gestureEnabled: false for a screen (totally possible I am missing a case).

I haven't worked with Objective C before but I think the issue is because of this.

{
// cancel touches in parent, this is needed to cancel RN touch events. For example when Touchable
// item is close to an edge and we start pulling from edge we want the Touchable to be cancelled.
// Without the below code the Touchable will remain active (highlighted) for the duration of back
// gesture and onPress may fire when we release the finger.
UIView *parent = _controller.view;
while (parent != nil && ![parent respondsToSelector:@selector(touchHandler)])
parent = parent.superview;
if (parent != nil) {
RCTTouchHandler *touchHandler = [parent performSelector:@selector(touchHandler)];
[touchHandler cancel];
[touchHandler reset];
}
RNSScreenView *topScreen = (RNSScreenView *)_controller.viewControllers.lastObject.view;
if (!topScreen.gestureEnabled || _controller.viewControllers.count < 2) {
return NO;
}
#if TARGET_OS_TV
return YES;
#else
if ([gestureRecognizer isKindOfClass:[RNSGestureRecognizer class]]) {
// if we do not set any explicit `semanticContentAttribute`, it is `UISemanticContentAttributeUnspecified` instead

Specifically, the part where touchHandler is cancelled and reset without checking if gesture's enabled for a screen.

I fixed it by applying this patch locally that tests if topScreen.gestureEnabled is truthy before cancelling/resetting touch handlers :

@@
diff --git a/node_modules/react-native-screens/ios/RNSScreenStack.m b/node_modules/react-native-screens/ios/RNSScreenStack.m
index 7b76e12..f44f11a 100644
--- a/node_modules/react-native-screens/ios/RNSScreenStack.m
+++ b/node_modules/react-native-screens/ios/RNSScreenStack.m
@@ -509,20 +509,22 @@

 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
 {
-  // cancel touches in parent, this is needed to cancel RN touch events. For example when Touchable
-  // item is close to an edge and we start pulling from edge we want the Touchable to be cancelled.
-  // Without the below code the Touchable will remain active (highlighted) for the duration of back
-  // gesture and onPress may fire when we release the finger.
-  UIView *parent = _controller.view;
-  while (parent != nil && ![parent respondsToSelector:@selector(touchHandler)]) parent = parent.superview;
-  if (parent != nil) {
-    RCTTouchHandler *touchHandler = [parent performSelector:@selector(touchHandler)];
-    [touchHandler cancel];
-    [touchHandler reset];
-  }
-  
   RNSScreenView *topScreen = (RNSScreenView *)_controller.viewControllers.lastObject.view;

+  if(topScreen.gestureEnabled) {
+    // cancel touches in parent, this is needed to cancel RN touch events. For example when Touchable
+    // item is close to an edge and we start pulling from edge we want the Touchable to be cancelled.
+    // Without the below code the Touchable will remain active (highlighted) for the duration of back
+    // gesture and onPress may fire when we release the finger.
+    UIView *parent = _controller.view;
+    while (parent != nil && ![parent respondsToSelector:@selector(touchHandler)]) parent = parent.superview;
+    if (parent != nil) {
+      RCTTouchHandler *touchHandler = [parent performSelector:@selector(touchHandler)];
+      [touchHandler cancel];
+      [touchHandler reset];
+    }
+  }
+  
   if (!topScreen.gestureEnabled || _controller.viewControllers.count < 2) {
     return NO;
   }

Steps To Reproduce

  1. Use native stack with gestureEnabled: false on iOS
  2. Use a slider. For ex this, just import and use <Slider maximumValue={100} />
  3. Start sliding from the left edge, you'll see that the slider is interrupted

Expected behavior

Touch handler shouldn't be interrupted when gestureEnabled:false

Actual behavior

Touch handler is interrupted

Snack or minimal code example

Package versions

  • React Native: 0.63.4
  • React Native Screens: 3.4.0
@owaiswiz owaiswiz changed the title RN Touch events are canceled even when {gestureEnabled: false} on false when using native stack navigation RN Touch events are canceled even when {gestureEnabled: false} on iOS when using native stack navigation Jul 21, 2021
@WoLewicki
Copy link
Member

If you are in the first screen in stack and the gesture is enabled on it, it will still cancel the touches even though there will be no option to go back. I think the cancelling code should only be triggered when the method is going to return YES. Maybe it would be best to refactor the code to have a single point of return with YES value, and trigger the canceling code before it. Could you submit such a PR?

@owaiswiz
Copy link
Author

owaiswiz commented Jul 22, 2021

I get what you mean. I might be able to look into that and submit a PR this weekend.

(Although, I must say that I have 0 experience with Obj-C. So if someone else can do it soonish then that, that might be a better option.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants