Conversation
There was a problem hiding this comment.
Pull request overview
Fixes iOS TextInput gesture callbacks not firing by adjusting how RNNativeViewGestureHandler binds to React Native TextInput views.
Changes:
- Import
RCTTextInputComponentViewand detect it inbindToView:. - Attempt to locate the backing
UITextField/UITextViewsubview and treat it as aUIControltarget for touch event callbacks. - Track and unbind the resolved control via a stored
_controlreference.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // touch events directly, then restore viewTag to the parent's react tag. | ||
| if (textInputChild != nil) { | ||
| [super bindToView:textInputChild]; |
There was a problem hiding this comment.
When binding multiline TextInput, the recognizer is attached to textInputChild but only self.viewTag is restored to the parent (view.reactTag). Touch callbacks that rely on needsPointerData (e.g. onTouchesDown/Move/Up/Cancelled) are dispatched by RNGestureHandlerPointerTracker using gestureHandler.recognizer.view.reactTag, so they’ll use the child UITextView tag (often nil) instead of the TextInput’s tag and won’t reach JS. Consider also aligning the event target for pointer-tracker events (e.g., ensure the bound child view has the parent reactTag for event dispatching, or change the native-view handler’s pointer-event dispatch path to use self.viewTag / findViewForEvents rather than recognizer.view.reactTag).
| // touch events directly, then restore viewTag to the parent's react tag. | |
| if (textInputChild != nil) { | |
| [super bindToView:textInputChild]; | |
| // touch events directly, then align both the handler's viewTag and the bound child's reactTag | |
| // with the parent's react tag so pointer-tracker events are dispatched to the correct JS target. | |
| if (textInputChild != nil) { | |
| [super bindToView:textInputChild]; | |
| textInputChild.reactTag = view.reactTag; |
…iewHandler.mm Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…native-gesture-handler into @mbert/textinput
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| [_gestureHandler sendTouchEventInState:[_gestureHandler state] | ||
| forViewWithTag:_gestureHandler.recognizer.view.reactTag]; | ||
| [_gestureHandler sendTouchEventInState:[_gestureHandler state] forViewWithTag:_gestureHandler.viewTag]; |
There was a problem hiding this comment.
sendEvent forwards pointer events using [_gestureHandler state], but some handlers (notably RNNativeViewGestureHandler in the UITextView path) manually dispatch BEGAN/ACTIVE via sendEventsInState without updating the internal _state. In that case, touch/pointer events will be emitted with state=BEGAN even after activation, which is inconsistent with other handlers and can confuse JS consumers that read event.state. Consider deriving the state for touch events from gestureHandler.lastState when available (falling back to state only when lastState is still UNDTERMINED).
| [_gestureHandler sendTouchEventInState:[_gestureHandler state] forViewWithTag:_gestureHandler.viewTag]; | |
| RNGestureHandlerState eventState = [_gestureHandler lastState]; | |
| if (eventState == RNGestureHandlerStateUndetermined) { | |
| eventState = [_gestureHandler state]; | |
| } | |
| [_gestureHandler sendTouchEventInState:eventState forViewWithTag:_gestureHandler.viewTag]; |
There was a problem hiding this comment.
In v3 consumers won't read event.state. Also Native gesture differs based on underlying view. IMO we can leave that since callbacks are ok (cc @j-piasecki)
There was a problem hiding this comment.
- We also have v1 and v1 API available in the package
- Native differes based on view/platform but I'd say sending random
BEGINstate change doesn't fall into that
| BOOL isInside = [self containsPointInView]; | ||
|
|
||
| if (!isInside && self.shouldCancelWhenOutside) { | ||
| [self sendEventsInState:RNGestureHandlerStateFailed | ||
| forViewWithTag:self.viewTag | ||
| withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO | ||
| withNumberOfTouches:event.allTouches.count | ||
| withPointerType:_pointerType]]; | ||
| } else { | ||
| [self sendEventsInState:RNGestureHandlerStateEnd | ||
| forViewWithTag:self.viewTag | ||
| withExtraData:[RNGestureHandlerEventExtraData forPointerInside:isInside | ||
| withNumberOfTouches:event.allTouches.count | ||
| withPointerType:_pointerType]]; | ||
| } |
There was a problem hiding this comment.
When shouldCancelWhenOutside is enabled and the touch ends outside, this path dispatches RNGestureHandlerStateFailed. For single-line TextInput/UIControl-backed views, leaving the bounds results in a cancellation flow (UIControlEventTouchCancel → CANCELLED). To keep behavior consistent across single-line and multiline TextInput, consider dispatching CANCELLED here (and setting pointerInside accordingly) rather than FAILED.
There was a problem hiding this comment.
I don't think it matters in that case. I've changed it to failed purposely. I can bring back Cancelled, but I don't think it makes much of a difference.
This reverts commit d99d12f.
Description
I've noticed that on
iOSnone of the gesture callbacks is triggered when using ourTextInput. This works fine onAndroidandweb.When we attach handler it is not attached to
UITextFieldnor toUITextView. Instead it is attached toRCTTextInputComponentView. Because of that we do not receive anyup/downevents.Test plan
Tested on the following code: