Skip to content

[iOS] Prevent duplicated events in Native gesture handling#4125

Merged
m-bert merged 3 commits into
mainfrom
@mbert/dont-duplicate-events-ios
Apr 28, 2026
Merged

[iOS] Prevent duplicated events in Native gesture handling#4125
m-bert merged 3 commits into
mainfrom
@mbert/dont-duplicate-events-ios

Conversation

@m-bert
Copy link
Copy Markdown
Collaborator

@m-bert m-bert commented Apr 28, 2026

Description

I've noticed that when Native gesture is not attached to UIControl it sends different events (e.g. events containing x and y positions). I've changed that to mimic behavior from #4102

Test plan

Tested on the following example
import { ScrollView, StyleSheet, View } from 'react-native';
import {
  GestureDetector,
  GestureHandlerRootView,
  ScrollView as RNGHScrollView,
  Touchable,
  useNativeGesture,
} from 'react-native-gesture-handler';

export default function App() {
  const g = useNativeGesture({
    onBegin: () => {
      console.log(Date.now(), 'gesture begin');
    },
    onActivate: () => {
      console.log(Date.now(), 'gesture activate');
    },
    onUpdate: (e) => {
      console.log(Date.now(), 'gesture update', e);
    },
    onDeactivate: () => {
      console.log(Date.now(), 'gesture deactivate');
    },
    onFinalize: () => {
      console.log(Date.now(), 'gesture finalize');
    },
    shouldCancelWhenOutside: false,
  });

  return (
    <GestureHandlerRootView style={styles.container}>
      <Touchable
        cancelOnLeave={false}
        style={{
          width: 150,
          height: 45,
          backgroundColor: 'crimson',
          borderRadius: 8,
        }}
      />

      <GestureDetector gesture={g}>
        <ScrollView style={[styles.scrollView, { backgroundColor: 'green' }]}>
          {BOXES.map((color, i) => (
            <View key={i} style={[styles.box, { backgroundColor: color }]} />
          ))}
        </ScrollView>
      </GestureDetector>
      <RNGHScrollView
        onBegin={() => {
          console.log(Date.now(), 'gesture begin');
        }}
        onActivate={() => {
          console.log(Date.now(), 'gesture activate');
        }}
        onUpdate={() => {
          console.log(Date.now(), 'gesture update');
        }}
        onDeactivate={() => {
          console.log(Date.now(), 'gesture deactivate');
        }}
        onFinalize={() => {
          console.log(Date.now(), 'gesture finalize');
        }}
        style={[styles.scrollView, { backgroundColor: 'blue' }]}>
        {BOXES.map((color, i) => (
          <View key={i} style={[styles.box, { backgroundColor: color }]} />
        ))}
      </RNGHScrollView>
    </GestureHandlerRootView>
  );
}

const BOXES = [
  '#ff6b6b',
  '#ffd93d',
  '#6bcb77',
  '#4d96ff',
  '#c77dff',
  '#ff9f43',
  '#ee5a24',
  '#0652dd',
  '#1289a7',
  '#d980fa',
];

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  scrollView: {
    width: 200,
    maxHeight: 250,
    marginVertical: 8,
  },
  box: {
    width: '100%',
    height: 60,
    marginBottom: 4,
  },
});

Copilot AI review requested due to automatic review settings April 28, 2026 07:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to prevent duplicate ACTIVE/update events being emitted by iOS native gesture handling (particularly for NativeView-based handling when not attached to a UIControl), aligning behavior with the approach from #4102.

Changes:

  • Added an overridable shouldSuppressActiveEvent: hook to RNGestureHandler and applied it during ACTIVE event dispatch.
  • Implemented suppression logic in RNNativeViewGestureHandler by comparing extraData.data to the last emitted active payload.
  • Moved _lastActiveExtraData cleanup to reset to ensure suppression state is cleared between gestures.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
packages/react-native-gesture-handler/apple/RNGestureHandler.mm Adds a suppression hook and uses it to early-return for duplicate ACTIVE events.
packages/react-native-gesture-handler/apple/RNGestureHandler.h Exposes the new suppression hook in the base handler interface.
packages/react-native-gesture-handler/apple/Handlers/RNNativeViewHandler.mm Implements duplicate suppression for NativeView handling and resets suppression state in reset.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/react-native-gesture-handler/apple/RNGestureHandler.h Outdated
@m-bert m-bert requested a review from j-piasecki April 28, 2026 07:47
Comment on lines +341 to +345
- (void)reset
{
[super reset];
_lastActiveExtraData = nil;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be sure, but this is called when attached to UIControls, right?

@m-bert m-bert merged commit 181a6f6 into main Apr 28, 2026
3 checks passed
@m-bert m-bert deleted the @mbert/dont-duplicate-events-ios branch April 28, 2026 09:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants