From 5b5b176c5ff0bfed7c552f01de4a8cb18f85b4b4 Mon Sep 17 00:00:00 2001 From: Hugo EXTRAT Date: Sun, 18 Jan 2026 12:28:25 +0100 Subject: [PATCH 1/5] fix: handles pointerEvents for pressable --- .../apple/RNGestureHandlerButton.h | 1 + .../apple/RNGestureHandlerButton.mm | 23 ++++++++++ .../RNGestureHandlerButtonComponentView.mm | 45 +++++++++++++++++++ .../apple/RNGestureHandlerButtonManager.mm | 24 ++++++++++ 4 files changed, 93 insertions(+) diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h index 6e789aacb2..f3d78079f0 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h @@ -28,6 +28,7 @@ @property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets; @property (nonatomic, assign) CGFloat borderRadius; @property (nonatomic) BOOL userEnabled; +@property (nonatomic, copy) NSString *pointerEvents; #if TARGET_OS_OSX && RCT_NEW_ARCH_ENABLED - (void)mountChildComponentView:(RNGHUIView *)childComponentView index:(NSInteger)index; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm index c9f8067bd6..933f16f609 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm @@ -93,6 +93,29 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event - (RNGHUIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { + NSString *pointerEvents = _pointerEvents ?: @"auto"; + + if ([pointerEvents isEqualToString:@"none"]) { + return nil; + } + + if ([pointerEvents isEqualToString:@"box-none"]) { + for (UIView *subview in [self.subviews reverseObjectEnumerator]) { + if (!subview.isHidden && subview.alpha > 0) { + CGPoint convertedPoint = [subview convertPoint:point fromView:self]; + UIView *hitView = [subview hitTest:convertedPoint withEvent:event]; + if (hitView != nil && [self shouldHandleTouch:hitView]) { + return hitView; + } + } + } + return nil; + } + + if ([pointerEvents isEqualToString:@"box-only"]) { + return [self pointInside:point withEvent:event] ? self : nil; + } + RNGHUIView *inner = [super hitTest:point withEvent:event]; while (inner && ![self shouldHandleTouch:inner]) { inner = inner.superview; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm index e19c8d531b..4641f30946 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm @@ -9,11 +9,27 @@ #import #import #import +#import #import "RNGestureHandlerButton.h" using namespace facebook::react; +static NSString *RCTPointerEventsToString(facebook::react::PointerEventsMode pointerEvents) +{ + switch (pointerEvents) { + case facebook::react::PointerEventsMode::None: + return @"none"; + case facebook::react::PointerEventsMode::BoxNone: + return @"box-none"; + case facebook::react::PointerEventsMode::BoxOnly: + return @"box-only"; + case facebook::react::PointerEventsMode::Auto: + default: + return @"auto"; + } +} + @interface RNGestureHandlerButtonComponentView () @end @@ -207,8 +223,37 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & _buttonView.hitTestEdgeInsets = UIEdgeInsetsMake( -newProps.hitSlop.top, -newProps.hitSlop.left, -newProps.hitSlop.bottom, -newProps.hitSlop.right); + if (!oldProps) { + _buttonView.pointerEvents = RCTPointerEventsToString(newProps.pointerEvents); + } else { + const auto &oldButtonProps = *std::static_pointer_cast(oldProps); + if (oldButtonProps.pointerEvents != newProps.pointerEvents) { + _buttonView.pointerEvents = RCTPointerEventsToString(newProps.pointerEvents); + } + } + [super updateProps:props oldProps:oldProps]; } + +#if !TARGET_OS_OSX +// Override hitTest to forward touches to _buttonView +// This is necessary because RCTViewComponentView's hitTest might handle pointerEvents +// from ViewProps and prevent touches from reaching _buttonView (which is the contentView). +// Since _buttonView has its own pointerEvents handling, we always forward to it. +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +{ + if (![self pointInside:point withEvent:event]) { + return nil; + } + + CGPoint buttonPoint = [self convertPoint:point toView:_buttonView]; + + UIView *hitView = [_buttonView hitTest:buttonPoint withEvent:event]; + + return hitView; +} +#endif + @end Class RNGestureHandlerButtonCls(void) diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonManager.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonManager.mm index 68345982cf..ecb6cb2ca1 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonManager.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonManager.mm @@ -1,6 +1,20 @@ #import "RNGestureHandlerButtonManager.h" #import "RNGestureHandlerButton.h" +static NSString *RCTPointerEventsToString(RCTPointerEvents pointerEvents) +{ + switch (pointerEvents) { + case RCTPointerEventsNone: + return @"none"; + case RCTPointerEventsBoxNone: + return @"box-none"; + case RCTPointerEventsBoxOnly: + return @"box-only"; + default: + return @"auto"; + } +} + @implementation RNGestureHandlerButtonManager RCT_EXPORT_MODULE(RNGestureHandlerButton) @@ -28,6 +42,16 @@ @implementation RNGestureHandlerButtonManager } } +RCT_CUSTOM_VIEW_PROPERTY(pointerEvents, RCTPointerEvents, RNGestureHandlerButton) +{ + if (json) { + RCTPointerEvents pointerEvents = [RCTConvert RCTPointerEvents:json]; + view.pointerEvents = RCTPointerEventsToString(pointerEvents); + } else { + view.pointerEvents = @"auto"; + } +} + - (RNGHUIView *)view { return (RNGHUIView *)[RNGestureHandlerButton new]; From 39ad6557032f17dd03c66db9c3ae9dd19d8d584c Mon Sep 17 00:00:00 2001 From: Hugo EXTRAT Date: Tue, 20 Jan 2026 11:51:47 +0100 Subject: [PATCH 2/5] chore: direct returns --- .../apple/RNGestureHandlerButtonComponentView.mm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm index 4641f30946..1732a0bb32 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm @@ -248,9 +248,7 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event CGPoint buttonPoint = [self convertPoint:point toView:_buttonView]; - UIView *hitView = [_buttonView hitTest:buttonPoint withEvent:event]; - - return hitView; + return [_buttonView hitTest:buttonPoint withEvent:event]; } #endif From 39f08f9b64bbe0e878c6a5e2886d5b49ab6ffcac Mon Sep 17 00:00:00 2001 From: Hugo EXTRAT Date: Tue, 20 Jan 2026 11:54:28 +0100 Subject: [PATCH 3/5] fix: refactors pointerEvents to use enum --- .../apple/RNGestureHandlerButton.h | 10 +++++++++- .../apple/RNGestureHandlerButton.mm | 9 +++++---- .../apple/RNGestureHandlerButtonComponentView.mm | 14 +++++++------- .../apple/RNGestureHandlerButtonManager.mm | 14 +++++++------- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h index f3d78079f0..95aee012ac 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h @@ -22,13 +22,21 @@ #else @interface RNGestureHandlerButton : UIControl #endif // TARGET_OS_OSX + +typedef NS_ENUM(NSInteger, RNGestureHandlerPointerEvents) { + RNGestureHandlerPointerEventsNone, + RNGestureHandlerPointerEventsBoxNone, + RNGestureHandlerPointerEventsBoxOnly, + RNGestureHandlerPointerEventsAuto +}; + /** * Insets used when hit testing inside this view. */ @property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets; @property (nonatomic, assign) CGFloat borderRadius; @property (nonatomic) BOOL userEnabled; -@property (nonatomic, copy) NSString *pointerEvents; +@property (nonatomic, assign) RNGestureHandlerPointerEvents pointerEvents; #if TARGET_OS_OSX && RCT_NEW_ARCH_ENABLED - (void)mountChildComponentView:(RNGHUIView *)childComponentView index:(NSInteger)index; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm index 933f16f609..7f5dcbc114 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm @@ -50,6 +50,7 @@ - (instancetype)init if (self) { _hitTestEdgeInsets = UIEdgeInsetsZero; _userEnabled = YES; + _pointerEvents = RNGestureHandlerPointerEventsAuto; #if !TARGET_OS_TV && !TARGET_OS_OSX [self setExclusiveTouch:YES]; #endif @@ -93,13 +94,13 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event - (RNGHUIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { - NSString *pointerEvents = _pointerEvents ?: @"auto"; + RNGestureHandlerPointerEvents pointerEvents = _pointerEvents; - if ([pointerEvents isEqualToString:@"none"]) { + if (pointerEvents == RNGestureHandlerPointerEventsNone) { return nil; } - if ([pointerEvents isEqualToString:@"box-none"]) { + if (pointerEvents == RNGestureHandlerPointerEventsBoxNone) { for (UIView *subview in [self.subviews reverseObjectEnumerator]) { if (!subview.isHidden && subview.alpha > 0) { CGPoint convertedPoint = [subview convertPoint:point fromView:self]; @@ -112,7 +113,7 @@ - (RNGHUIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event return nil; } - if ([pointerEvents isEqualToString:@"box-only"]) { + if (pointerEvents == RNGestureHandlerPointerEventsBoxOnly) { return [self pointInside:point withEvent:event] ? self : nil; } diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm index 1732a0bb32..2ae6f8eea8 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm @@ -15,18 +15,18 @@ using namespace facebook::react; -static NSString *RCTPointerEventsToString(facebook::react::PointerEventsMode pointerEvents) +static RNGestureHandlerPointerEvents RCTPointerEventsToEnum(facebook::react::PointerEventsMode pointerEvents) { switch (pointerEvents) { case facebook::react::PointerEventsMode::None: - return @"none"; + return RNGestureHandlerPointerEventsNone; case facebook::react::PointerEventsMode::BoxNone: - return @"box-none"; + return RNGestureHandlerPointerEventsBoxNone; case facebook::react::PointerEventsMode::BoxOnly: - return @"box-only"; + return RNGestureHandlerPointerEventsBoxOnly; case facebook::react::PointerEventsMode::Auto: default: - return @"auto"; + return RNGestureHandlerPointerEventsAuto; } } @@ -224,11 +224,11 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & -newProps.hitSlop.top, -newProps.hitSlop.left, -newProps.hitSlop.bottom, -newProps.hitSlop.right); if (!oldProps) { - _buttonView.pointerEvents = RCTPointerEventsToString(newProps.pointerEvents); + _buttonView.pointerEvents = RCTPointerEventsToEnum(newProps.pointerEvents); } else { const auto &oldButtonProps = *std::static_pointer_cast(oldProps); if (oldButtonProps.pointerEvents != newProps.pointerEvents) { - _buttonView.pointerEvents = RCTPointerEventsToString(newProps.pointerEvents); + _buttonView.pointerEvents = RCTPointerEventsToEnum(newProps.pointerEvents); } } diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonManager.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonManager.mm index ecb6cb2ca1..a7cc615816 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonManager.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonManager.mm @@ -1,17 +1,17 @@ #import "RNGestureHandlerButtonManager.h" #import "RNGestureHandlerButton.h" -static NSString *RCTPointerEventsToString(RCTPointerEvents pointerEvents) +static RNGestureHandlerPointerEvents RCTPointerEventsToEnum(RCTPointerEvents pointerEvents) { switch (pointerEvents) { case RCTPointerEventsNone: - return @"none"; + return RNGestureHandlerPointerEventsNone; case RCTPointerEventsBoxNone: - return @"box-none"; + return RNGestureHandlerPointerEventsBoxNone; case RCTPointerEventsBoxOnly: - return @"box-only"; + return RNGestureHandlerPointerEventsBoxOnly; default: - return @"auto"; + return RNGestureHandlerPointerEventsAuto; } } @@ -46,9 +46,9 @@ @implementation RNGestureHandlerButtonManager { if (json) { RCTPointerEvents pointerEvents = [RCTConvert RCTPointerEvents:json]; - view.pointerEvents = RCTPointerEventsToString(pointerEvents); + view.pointerEvents = RCTPointerEventsToEnum(pointerEvents); } else { - view.pointerEvents = @"auto"; + view.pointerEvents = RNGestureHandlerPointerEventsAuto; } } From b82c1860dca96fc0d9ed16b09a659bad481f5d34 Mon Sep 17 00:00:00 2001 From: Hugo EXTRAT Date: Tue, 20 Jan 2026 13:39:27 +0100 Subject: [PATCH 4/5] chore: extracts pointer events enum --- .../react-native-gesture-handler/apple/RNGestureHandler.h | 1 + .../apple/RNGestureHandlerButton.h | 8 -------- .../apple/RNGestureHandlerPointerEvents.h | 6 ++++++ 3 files changed, 7 insertions(+), 8 deletions(-) create mode 100644 packages/react-native-gesture-handler/apple/RNGestureHandlerPointerEvents.h diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.h b/packages/react-native-gesture-handler/apple/RNGestureHandler.h index 87ba86c173..750caba184 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.h @@ -2,6 +2,7 @@ #import "RNGestureHandlerActionType.h" #import "RNGestureHandlerDirection.h" #import "RNGestureHandlerEvents.h" +#import "RNGestureHandlerPointerEvents.h" #import "RNGestureHandlerPointerTracker.h" #import "RNGestureHandlerPointerType.h" #import "RNGestureHandlerState.h" diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h index 95aee012ac..a699daec48 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h @@ -22,14 +22,6 @@ #else @interface RNGestureHandlerButton : UIControl #endif // TARGET_OS_OSX - -typedef NS_ENUM(NSInteger, RNGestureHandlerPointerEvents) { - RNGestureHandlerPointerEventsNone, - RNGestureHandlerPointerEventsBoxNone, - RNGestureHandlerPointerEventsBoxOnly, - RNGestureHandlerPointerEventsAuto -}; - /** * Insets used when hit testing inside this view. */ diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerPointerEvents.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerPointerEvents.h new file mode 100644 index 0000000000..b9ee2f28eb --- /dev/null +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerPointerEvents.h @@ -0,0 +1,6 @@ +typedef NS_ENUM(NSInteger, RNGestureHandlerPointerEvents) { + RNGestureHandlerPointerEventsNone, + RNGestureHandlerPointerEventsBoxNone, + RNGestureHandlerPointerEventsBoxOnly, + RNGestureHandlerPointerEventsAuto +}; \ No newline at end of file From 0141297e402efd6da89a75943914d7af6474b254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 21 Jan 2026 16:38:01 +0100 Subject: [PATCH 5/5] Cosmetic changes --- .../apple/RNGestureHandlerPointerEvents.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerPointerEvents.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerPointerEvents.h index b9ee2f28eb..b2fe715bdd 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerPointerEvents.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerPointerEvents.h @@ -1,6 +1,8 @@ +#import + typedef NS_ENUM(NSInteger, RNGestureHandlerPointerEvents) { RNGestureHandlerPointerEventsNone, RNGestureHandlerPointerEventsBoxNone, RNGestureHandlerPointerEventsBoxOnly, RNGestureHandlerPointerEventsAuto -}; \ No newline at end of file +};