From 9a4f06d1aee5fc12fc6b8d050481259d368b3611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 5 Aug 2026 18:47:32 +0200 Subject: [PATCH 1/2] Fix manual activation --- .../apple/RNManualActivationRecognizer.m | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/react-native-gesture-handler/apple/RNManualActivationRecognizer.m b/packages/react-native-gesture-handler/apple/RNManualActivationRecognizer.m index 2ecaef6e2e..7b522ca4b8 100644 --- a/packages/react-native-gesture-handler/apple/RNManualActivationRecognizer.m +++ b/packages/react-native-gesture-handler/apple/RNManualActivationRecognizer.m @@ -22,24 +22,31 @@ - (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler - (void)handleGesture:(UIGestureRecognizer *)recognizer { if (recognizer.state == UIGestureRecognizerStateBegan) { +#if TARGET_OS_OSX + // On iOS, this recognizer completing is enough to deny the handler's recognizer — + // UIKit fails a recognizer whose required-to-fail dependency recognizes. AppKit + // does not do this reliably: completing the blocker can flush the dependent + // recognizer's buffered recognition instead of discarding it. Fail it explicitly. + _handler.recognizer.state = UIGestureRecognizerStateFailed; +#endif self.state = UIGestureRecognizerStateEnded; [self reset]; } } #if TARGET_OS_OSX -- (void)mouseUp:(NSEvent *)event +- (void)mouseDown:(NSEvent *)event { - [super mouseUp:event]; + [super mouseDown:event]; - _activePointers -= 1; + _activePointers += 1; } -- (void)mouseDown:(NSEvent *)event +- (void)mouseUp:(NSEvent *)event { - [super mouseDown:event]; + [super mouseUp:event]; - _activePointers += 1; + _activePointers -= 1; if (_activePointers == 0) { self.state = UIGestureRecognizerStateBegan; @@ -80,6 +87,10 @@ - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)ev - (void)reset { self.enabled = YES; + // A recognizer that has reset tracks no pointers. On iOS a stale count is already + // prevented by touchesCancelled; on macOS there is no cancellation callback, so a + // mouse-up missed while the recognizer was disabled (after activate()) would leak it. + _activePointers = 0; [super reset]; } @@ -106,4 +117,15 @@ - (BOOL)shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGe return NO; } +#if TARGET_OS_OSX +// On iOS the method above is a UIGestureRecognizer subclass override, called by the system +// to establish the failure requirement. NSGestureRecognizer has no such subclass hook — +// AppKit only consults the delegate, so forward the delegate callback to the shared logic. +- (BOOL)gestureRecognizer:(NSGestureRecognizer *)gestureRecognizer + shouldBeRequiredToFailByGestureRecognizer:(NSGestureRecognizer *)otherGestureRecognizer +{ + return [self shouldBeRequiredToFailByGestureRecognizer:otherGestureRecognizer]; +} +#endif + @end From c96efd425b98974f5929aa0c96ddf06a47fd1bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 6 Aug 2026 11:50:18 +0200 Subject: [PATCH 2/2] Comments --- .../apple/RNManualActivationRecognizer.m | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/react-native-gesture-handler/apple/RNManualActivationRecognizer.m b/packages/react-native-gesture-handler/apple/RNManualActivationRecognizer.m index 7b522ca4b8..d99b1eed3c 100644 --- a/packages/react-native-gesture-handler/apple/RNManualActivationRecognizer.m +++ b/packages/react-native-gesture-handler/apple/RNManualActivationRecognizer.m @@ -26,7 +26,7 @@ - (void)handleGesture:(UIGestureRecognizer *)recognizer // On iOS, this recognizer completing is enough to deny the handler's recognizer — // UIKit fails a recognizer whose required-to-fail dependency recognizes. AppKit // does not do this reliably: completing the blocker can flush the dependent - // recognizer's buffered recognition instead of discarding it. Fail it explicitly. + // recognizer's buffered recognition instead of discarding it. _handler.recognizer.state = UIGestureRecognizerStateFailed; #endif self.state = UIGestureRecognizerStateEnded; @@ -87,9 +87,6 @@ - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)ev - (void)reset { self.enabled = YES; - // A recognizer that has reset tracks no pointers. On iOS a stale count is already - // prevented by touchesCancelled; on macOS there is no cancellation callback, so a - // mouse-up missed while the recognizer was disabled (after activate()) would leak it. _activePointers = 0; [super reset]; }