Skip to content

Commit 18b08dd

Browse files
authored
[iOS] Keep button hit testing within its own subtree (#4495)
## Description Fixes #4494. When a disabled `RNGestureHandlerButton` has no eligible touch target, the hit-test loop can continue past the button and return an ancestor outside its subtree. This change returns `nil` when the search reaches the button itself without finding an eligible target. Eligible descendants can still be selected before reaching that boundary. The guard prevents the SwiftUI-backed blur crash in my device testing. The suspected connection between returning a hosting ancestor and recursive hit testing is described in the linked issue. ## Test plan Tested the same boundary guard in my app’s test screen on an iPhone 16 Pro running iOS 26.5.2, using an Expo SDK 57 development build with React Native 0.86.3 and Gesture Handler 3.2.1. React Native’s experimental `enableSwiftUIBasedFilters` flag was enabled. - Original behaviour: tapping the disabled outer area crashes. - Boundary guard enabled, pointer-events workaround off: tapping the same area no longer crashes. Reproduction project and build instructions: https://github.com/rileysay/rngh-swiftui-hit-test-repro The standalone project has built successfully on EAS. The device observations above were recorded using the test screen in my original app. AI assistance: I used Codex to help investigate the native code, prepare the reproduction project, and propose this patch. I personally verified the device observations above.
1 parent 4768ffe commit 18b08dd

1 file changed

Lines changed: 18 additions & 1 deletion

File tree

‎packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1193,13 +1193,22 @@ - (void)mouseExited:(NSEvent *)event
11931193

11941194
- (void)mouseDown:(NSEvent *)event
11951195
{
1196+
if (!_userEnabled) {
1197+
return;
1198+
}
1199+
1200+
11961201
_isTouchInsideBounds = YES;
11971202
[self handleAnimatePressIn];
11981203
[super mouseDown:event];
11991204
}
12001205

12011206
- (void)mouseUp:(NSEvent *)event
12021207
{
1208+
if (!_userEnabled) {
1209+
return;
1210+
}
1211+
12031212
NSPoint locationInView = [self convertPoint:[event locationInWindow] fromView:nil];
12041213
_isHovered = NSPointInRect(locationInView, self.bounds);
12051214
[self recordHoverSampleForMouseEvent:event];
@@ -1212,6 +1221,10 @@ - (void)mouseUp:(NSEvent *)event
12121221

12131222
- (void)mouseDragged:(NSEvent *)event
12141223
{
1224+
if (!_userEnabled) {
1225+
return;
1226+
}
1227+
12151228
NSPoint locationInWindow = [event locationInWindow];
12161229
NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil];
12171230
BOOL currentlyInside = NSPointInRect(locationInView, self.bounds);
@@ -1244,6 +1257,10 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
12441257

12451258
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
12461259
{
1260+
if (!_userEnabled) {
1261+
return NO;
1262+
}
1263+
12471264
_isTouchInsideBounds = YES;
12481265
// A pencil's hover-out arrives just before touch-down but only schedules the
12491266
// clear, so `_isHovered` still reflects the open hover. Under Reduce Motion
@@ -1469,7 +1486,7 @@ - (RNGHUIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
14691486
}
14701487

14711488
RNGHUIView *inner = [super hitTest:point withEvent:event];
1472-
while (inner && ![self shouldHandleTouch:inner atPoint:point]) {
1489+
while (inner && inner != self && ![self shouldHandleTouch:inner atPoint:point]) {
14731490
inner = inner.superview;
14741491
}
14751492
return inner;

0 commit comments

Comments
 (0)