Commit 5634e8a
fix(iOS): Respect cancelsTouchesInView in RCTSurfaceTouchHandler (#54755)
Summary:
Respect `cancelsTouchesInView` property when deciding whether to cancel touches in `RCTSurfaceTouchHandler`
## Motivation
Currently, `RCTSurfaceTouchHandler` unconditionally cancels touches whenever `canBePreventedByGestureRecognizer:` returns `YES`, regardless of the other gesture recognizer's `cancelsTouchesInView` property:
```objc
if (canBePrevented) {
[self _cancelTouches]; // Always cancels!
}
```
This creates an issue when developers add custom gesture recognizers to ancestor views of React Native view controllers with `cancelsTouchesInView = NO`. Even though the developer explicitly indicates they don't want to cancel touches in the view hierarchy, RCT still cancels them.
**Example scenario:**
```objc
// Custom gesture recognizer added to parent view
UIPanGestureRecognizer *customGesture = [[UIPanGestureRecognizer alloc] init...];
customGesture.cancelsTouchesInView = NO; // Explicitly not canceling touches
[parentView addGestureRecognizer:customGesture];
// But RCTSurfaceTouchHandler still cancels touches unconditionally
```
This breaks the intended behavior where `cancelsTouchesInView = NO` should allow both the gesture and underlying touch handlers to work together.
In our case, React Native is used in a brownfield setup inside an existing iOS application.
When a new React Native–powered view controller is presented, we attach additional gesture recognizers on ancestor view controllers to track LCP (Largest Contentful Paint) and other performance metrics. These gesture recognizers are configured with `cancelsTouchesInView = NO` because they are intended to observe gestures without interfering with the existing touch handling in the React Native view hierarchy.
However, due to the current behavior in `RCTSurfaceTouchHandler`, any time these tracking gesture recognizers can prevent the React Native touch handler, all touches inside the React Native hierarchy are still cancelled, even though `cancelsTouchesInView` is explicitly set to `NO`. In practice this makes `Pressable` and other touchables stop responding as soon as the tracking gesture begins recognizing.
We have been maintaining a local patch equivalent to this change in our production app to restore the expected UIKit behavior. This PR upstreams that fix so that brownfield integrations and other setups that rely on `cancelsTouchesInView = NO` can work correctly without custom patches.
## Changes
Modified the condition to check `otherGestureRecognizer.cancelsTouchesInView` before canceling touches:
```objc
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
BOOL canBePrevented = [self canBePreventedByGestureRecognizer:otherGestureRecognizer];
if (canBePrevented && otherGestureRecognizer.cancelsTouchesInView) {
[self _cancelTouches];
}
return NO;
}
```
## Why This Change Is Correct
1. **Respects UIKit conventions**: The `cancelsTouchesInView` property is the standard UIKit way to control whether a gesture recognizer cancels touches. This change honors that contract.
2. **Preserves original intent**: The original fix (a9bc385) was designed to cancel touches during interactive view controller dismissal. Since system gesture recognizers use `cancelsTouchesInView = YES` by default, this behavior is preserved.
3. **Enables flexible gesture composition**: Developers can now explicitly control whether their custom gestures should cancel RN touches by setting `cancelsTouchesInView` appropriately.
4. **Logical consistency**: "Only cancel touches when the other gesture recognizer intends to cancel touches" is more semantically correct than "always cancel when preventable."
[iOS] [Fixed] - Respect cancelsTouchesInView when canceling touches in RCTSurfaceTouchHandler
Pull Request resolved: #54755
Test Plan:
**Existing behavior (should remain unchanged):**
- Interactive view controller dismissal still cancels Pressable highlights
- Standard UIKit gesture recognizers (pan, swipe, etc.) work as before
**New behavior (fixes the issue):**
1. Add a custom gesture recognizer to an ancestor view with `cancelsTouchesInView = NO`
2. Verify that React Native touchables/Pressables continue to respond to touches
3. Verify that the custom gesture and RN touch handlers can work simultaneously
**Testing:**
```objc
// Test case: Custom gesture with cancelsTouchesInView = NO
UIPanGestureRecognizer *customGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:selector(handlePan:)];
customGesture.cancelsTouchesInView = NO;
[self.view addGestureRecognizer:customGesture];
// Expected: Both customGesture and RN Pressable should respond
```
## Changelog:
[iOS] [Fixed] - Respect `cancelsTouchesInView` when canceling touches in `RCTSurfaceTouchHandler`
Reviewed By: fabriziocucci
Differential Revision: D88174531
Pulled By: javache
fbshipit-source-id: ed058791a4eef7401fd7198d4c8a2515a6b6b7521 parent 0874746 commit 5634e8a
1 file changed
+1
-1
lines changedLines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
399 | 399 | | |
400 | 400 | | |
401 | 401 | | |
402 | | - | |
| 402 | + | |
403 | 403 | | |
404 | 404 | | |
405 | 405 | | |
| |||
0 commit comments