[iOS] Change when Native gesture states are changed#4036
[iOS] Change when Native gesture states are changed#4036
Native gesture states are changed#4036Conversation
There was a problem hiding this comment.
Pull request overview
This PR adjusts iOS Native gesture event/state emission to better align with Android behavior, reducing platform-specific workarounds in JS components (notably Pressable/buttons).
Changes:
- Update v3
Pressableto avoid usingNative.onActivateon iOS and to always run finalize cleanup. - Simplify v3
GestureButtonslong-press scheduling to consistently start fromonBegin(removing platform branching). - Modify iOS
RNNativeViewHandlerto emitBEGANon touch-down and to forward additional drag-inside/outside pointer updates; update Pressable iOS state machine config accordingly.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| packages/react-native-gesture-handler/src/v3/components/Pressable.tsx | Adjusts iOS handling to rely on onBegin/touch-down tracking instead of onActivate, and unifies finalize cleanup. |
| packages/react-native-gesture-handler/src/v3/components/GestureButtons.tsx | Removes platform-specific workaround by starting long-press logic from onBegin for all platforms. |
| packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts | Switches iOS Pressable state machine to trigger handlePressIn on NATIVE_BEGIN instead of NATIVE_START. |
| packages/react-native-gesture-handler/apple/Handlers/RNNativeViewHandler.mm | Changes iOS UIControl-based native handler state emission (touch-down now BEGAN) and adds drag-inside/outside event forwarding. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| [self sendEventsInState:RNGestureHandlerStateBegan | ||
| forViewWithTag:sender.reactTag | ||
| withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES | ||
| withNumberOfTouches:event.allTouches.count |
There was a problem hiding this comment.
handleTouchDown now emits RNGestureHandlerStateBegan instead of ...StateActive. With the current JS button implementations (both legacy src/components/GestureButtons.tsx and v3 src/v3/components/GestureButtons.tsx), the “pressed/active” UI feedback is driven by the gesture entering ACTIVE while the finger is down. If iOS stays in BEGAN until drag/finish, onActiveStateChange(true)/pressed underlay/opacity won’t update on touch down (it may only flash at release due to the synthetic ACTIVE sent right before END in sendEventsInState). If the intent is to start sending BEGAN for parity but keep pressed feedback, consider sending BEGAN and then immediately ACTIVE on touch down (or otherwise ensuring iOS enters ACTIVE at press start).
| [control addTarget:self | ||
| action:@selector(handleDragInside:forEvent:) | ||
| forControlEvents:UIControlEventTouchDragInside]; | ||
| [control addTarget:self | ||
| action:@selector(handleDragOutside:forEvent:) | ||
| forControlEvents:UIControlEventTouchDragOutside]; |
There was a problem hiding this comment.
So this means that ios buttons will also spam events when pointer moves when pressed?
| [self sendEventsInState:RNGestureHandlerStateBegan | ||
| forViewWithTag:sender.reactTag | ||
| withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES |
There was a problem hiding this comment.
Hmm, maybe the "correct" approach would be to send begin and active immediately when pressed down (and do the same on other platforms)?
This may be weird that the button isn't "active" unless the pointer moves.
There was a problem hiding this comment.
I also thought about it. This would also match current web logic.
| if (onLongPress) { | ||
| longPressTimeout.current = setTimeout(wrappedLongPress, delayLongPress); | ||
| } | ||
| // iOS, macOS. Web has its own implementation of button. |
There was a problem hiding this comment.
Why remove the pointerInside check?
| } | ||
|
|
||
| props.onBegin?.(e); | ||
| if (Platform.OS === 'android') { |
Description
Nativegesture is specific and its behavior differs across platforms. This leads to strange workarounds in our codebase (e.g. buttons).In this PR I'm changing how events are sent on native side on iOS, such that it is closer to Android behavior.
Test plan
Tested on expo-example app (buttons / Pressable)