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 6e789aacb2..a699daec48 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, 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 c9f8067bd6..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,6 +94,29 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event - (RNGHUIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { + RNGestureHandlerPointerEvents pointerEvents = _pointerEvents; + + if (pointerEvents == RNGestureHandlerPointerEventsNone) { + return nil; + } + + if (pointerEvents == RNGestureHandlerPointerEventsBoxNone) { + 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 == RNGestureHandlerPointerEventsBoxOnly) { + 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..2ae6f8eea8 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 RNGestureHandlerPointerEvents RCTPointerEventsToEnum(facebook::react::PointerEventsMode pointerEvents) +{ + switch (pointerEvents) { + case facebook::react::PointerEventsMode::None: + return RNGestureHandlerPointerEventsNone; + case facebook::react::PointerEventsMode::BoxNone: + return RNGestureHandlerPointerEventsBoxNone; + case facebook::react::PointerEventsMode::BoxOnly: + return RNGestureHandlerPointerEventsBoxOnly; + case facebook::react::PointerEventsMode::Auto: + default: + return RNGestureHandlerPointerEventsAuto; + } +} + @interface RNGestureHandlerButtonComponentView () @end @@ -207,8 +223,35 @@ - (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 = RCTPointerEventsToEnum(newProps.pointerEvents); + } else { + const auto &oldButtonProps = *std::static_pointer_cast(oldProps); + if (oldButtonProps.pointerEvents != newProps.pointerEvents) { + _buttonView.pointerEvents = RCTPointerEventsToEnum(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]; + + return [_buttonView hitTest:buttonPoint withEvent:event]; +} +#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..a7cc615816 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 RNGestureHandlerPointerEvents RCTPointerEventsToEnum(RCTPointerEvents pointerEvents) +{ + switch (pointerEvents) { + case RCTPointerEventsNone: + return RNGestureHandlerPointerEventsNone; + case RCTPointerEventsBoxNone: + return RNGestureHandlerPointerEventsBoxNone; + case RCTPointerEventsBoxOnly: + return RNGestureHandlerPointerEventsBoxOnly; + default: + return RNGestureHandlerPointerEventsAuto; + } +} + @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 = RCTPointerEventsToEnum(pointerEvents); + } else { + view.pointerEvents = RNGestureHandlerPointerEventsAuto; + } +} + - (RNGHUIView *)view { return (RNGHUIView *)[RNGestureHandlerButton new]; 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..b2fe715bdd --- /dev/null +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerPointerEvents.h @@ -0,0 +1,8 @@ +#import + +typedef NS_ENUM(NSInteger, RNGestureHandlerPointerEvents) { + RNGestureHandlerPointerEventsNone, + RNGestureHandlerPointerEventsBoxNone, + RNGestureHandlerPointerEventsBoxOnly, + RNGestureHandlerPointerEventsAuto +};