diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm index 6f14b1ee89..5802b23914 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm @@ -422,6 +422,16 @@ - (void)handleGesture:(UIGestureRecognizer *)recognizer _state = [self recognizerState]; + // When the state manager activates a handler it dispatches ACTIVE itself and sets the recognizer's + // state to Began so that UIKit keeps driving it. UIKit delivers the action for that + // programmatic transition asynchronously, so this method runs later with the recognizer in Began + // while ACTIVE has already been sent. `sendEventsInState:` drops that stale event, but by then + // `_state` (which touch events report) would have been overwritten. Keep the handler active instead. + if (!fromReset && _state == RNGestureHandlerStateBegan && _lastState == RNGestureHandlerStateActive) { + _state = RNGestureHandlerStateActive; + return; + } + // From iOS 26.0 when recognizers are reset, their state is also changed to UIGestureRecognizerStatePossible. // This means that our logic that relies on sending events in `reset` methods doesn't work properly. The bug that // `onFinalize` was not send after `onBegin` happened because both recognizer states, `Began` and `Possible`, are diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm index f605dffdd5..23ac909c1c 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm @@ -245,12 +245,18 @@ - (void)setGestureStateSync:(int)state forHandler:(int)handlerTag } else if (state == 3) { // CANCELLED handler.recognizer.state = RNGHGestureRecognizerStateCancelled; } else if (state == 4) { // ACTIVE - // We don't allow activation of gestures which haven't received any touches - if (handler.lastState == RNGestureHandlerStateUndetermined) { + if (handler.lastState == RNGestureHandlerStateUndetermined && handler.pointerTracker.trackedPointersCount > 0) { + [handler handleGesture:handler.recognizer inState:RNGestureHandlerStateBegan fromManualStateChange:YES]; + } + + if (handler.lastState != RNGestureHandlerStateBegan) { return; } + [handler stopActivationBlocker]; handler.recognizer.state = RNGHGestureRecognizerStateBegan; + // Don't wait for the next touch move to report ACTIVE. + [handler handleGesture:handler.recognizer inState:RNGestureHandlerStateActive fromManualStateChange:YES]; } else if (state == 5) { // ENDED handler.recognizer.state = RNGHGestureRecognizerStateEnded; }