SIGN IN SIGN UP

[iOS] Fix touch events never being delivered to `VirtualDetector` handlers (#4392)

## Description

On iOS, handlers attached through `VirtualGestureDetector` never
received `onTouches*` callbacks: state events
(`onBegin`/`onActivate`/`onFinalize`) arrived, but every pointer-data
event was silently dropped. Android and web deliver all of them for the
same code.

Two spots in the touch-event path were only half-migrated when the
virtual detector joined the native one — both still special-cased
`RNGestureHandlerActionTypeNativeDetector` alone:

1. `RNGestureHandler.sendTouchEventInState` routed touch events through
the codegen detector path (`sendNativeTouchEventForGestureHandler`,
which resolves its target via `findViewForEvents` → `hostDetectorView`)
only for the native detector. A virtual handler's touch events fell into
the legacy branch that builds a `reactTag`-routed event.
2. `RNGestureHandlerPointerTracker.sendEvent` drops events when the
recognizer's view has no `reactTag`, with an exemption only for the
native detector. The virtual recognizer's view has no `reactTag`, so the
guard swallowed every touch event for virtual handlers unconditionally.

Both checks now use the existing `usesNativeOrVirtualDetector` helper,
mirroring how Android's `RNGestureHandlerEventDispatcher` treats the two
detector types uniformly (single branch, delivery through the detector
view). With (1) in place, detector-attached touch events never involve
the view's `reactTag`, which is what makes the widened exemption in (2)
safe.

## Test plan

<details>
<summary>Tested on the following code:</summary>

```tsx
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {
  GestureDetector,
  InterceptingGestureDetector,
  usePanGesture,
  useTapGesture,
  VirtualGestureDetector,
} from 'react-native-gesture-handler';

export default function EmptyExample() {
  const interceptingTap = useTapGesture({ disableReanimated: true });

  const virtualTapWithTouches = useTapGesture({
    disableReanimated: true,
    onActivate: () => console.log('[virtual-tap-touches] onActivate'),
    onFinalize: () => console.log('[virtual-tap-touches] onFinalize'),
    onTouchesDown: () => console.log('[virtual-tap-touches] onTouchesDown'),
    onTouchesUp: () => console.log('[virtual-tap-touches] onTouchesUp'),
  });

  const nativePan = usePanGesture({
    disableReanimated: true,
    onBegin: () => console.log('[native] onBegin'),
    onActivate: () => console.log('[native] onActivate'),
    onDeactivate: () => console.log('[native] onDeactivate'),
    onFinalize: () => console.log('[native] onFinalize'),
    onTouchesDown: () => console.log('[native] onTouchesDown'),
    onTouchesMove: () => console.log('[native] onTouchesMove'),
    onTouchesUp: () => console.log('[native] onTouchesUp'),
    onTouchesCancel: () => console.log('[native] onTouchesCancel'),
  });

  return (
    <View style={styles.container}>
      <InterceptingGestureDetector gesture={interceptingTap}>
        <Text style={styles.paragraph}>
          Virtual detector target:{' '}
          <VirtualGestureDetector gesture={virtualTapWithTouches}>
            <Text style={styles.virtualTarget}>TAP ME</Text>
          </VirtualGestureDetector>
        </Text>
      </InterceptingGestureDetector>

      <GestureDetector gesture={nativePan}>
        <View style={styles.box}>
          <Text style={styles.boxLabel}>native detector control</Text>
        </View>
      </GestureDetector>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center', gap: 24, padding: 24 },
  paragraph: { fontSize: 18, textAlign: 'center' },
  virtualTarget: { fontSize: 22, fontWeight: 'bold', color: 'darkgreen' },
  box: {
    width: 220,
    height: 100,
    borderRadius: 12,
    backgroundColor: 'steelblue',
    justifyContent: 'center',
    alignItems: 'center',
  },
  boxLabel: { color: 'white' },
});
```

</details>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
M
Michał Bert committed
7b35938179972ec3eb42dc0be490b8df4c8ffa7c
Parent: 73c51c7
Committed by GitHub <noreply@github.com> on 8/7/2026, 9:33:23 AM