Skip to content

Commit 1df5419

Browse files
m-bertj-piasecki
authored andcommitted
[iOS] Fix GestureDetector on Text (#3591)
## Description ### Problem Currently the following configuration: ```jsx <GestureDetector ... > <Text> ... </Text> </GestureDetector> ``` does not work on `iOS`. This is due to change `react-native` introduced in 0.79 - `hitTest` in `RCTParagraphTextView` now returns `nil` by default ([see here](https://github.com/facebook/react-native/blob/dcbbf275cbc4150820691a4fbc254b198cc92bdd/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm#L379)). This results in native `UIGestureRecognizer` not responding to touches. ### Solution We no longer attach native recognizer to `RCTParagraphTextView`, but to its parent - `RCTParagraphComponentView`. The problem with this approach is that `handleGesture` method uses `reactTag` property, which on `RCTParagraphComponentView` is `nil`. This is why we use `reactTag` from `RCTParagraphTextView` when sending event to `JS` side. Fixes #3581 ## Test plan <details> <summary>Tested on the following code:</summary> ```jsx import React from 'react'; import { StyleSheet, Text } from 'react-native'; import { Gesture, GestureDetector, GestureHandlerRootView, } from 'react-native-gesture-handler'; export default function EmptyExample() { const g = Gesture.Tap().onEnd(() => { console.log('Tapped!'); }); return ( <GestureHandlerRootView style={styles.container}> <GestureDetector gesture={g}> <Text> Click me <Text> Me too! </Text> </Text> </GestureDetector> </GestureHandlerRootView> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, }); ``` </details>
1 parent 207d1ba commit 1df5419

File tree

2 files changed

+45
-6
lines changed

2 files changed

+45
-6
lines changed

packages/react-native-gesture-handler/apple/RNGestureHandler.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@
7272
@property (nonatomic) BOOL needsPointerData;
7373
@property (nonatomic) BOOL manualActivation;
7474

75+
#if RCT_NEW_ARCH_ENABLED
76+
- (BOOL)isViewParagraphComponent:(nullable RNGHUIView *)view;
77+
#endif
78+
- (nonnull RNGHUIView *)chooseViewForInteraction:(nonnull UIGestureRecognizer *)recognizer;
7579
- (void)bindToView:(nonnull RNGHUIView *)view;
7680
- (void)unbindFromView;
7781
- (void)resetConfig NS_REQUIRES_SUPER;

packages/react-native-gesture-handler/apple/RNGestureHandler.mm

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#import <React/UIView+React.h>
1212

1313
#ifdef RCT_NEW_ARCH_ENABLED
14+
#import <React/RCTParagraphComponentView.h>
1415
#import <React/RCTScrollViewComponentView.h>
1516
#else
1617
#import <React/RCTScrollView.h>
@@ -215,15 +216,32 @@ - (UITouchType)getPointerType
215216
return (UITouchType)_pointerType;
216217
}
217218

219+
#if RCT_NEW_ARCH_ENABLED
220+
- (BOOL)isViewParagraphComponent:(RNGHUIView *)view
221+
{
222+
return [view isKindOfClass:[RCTParagraphComponentView class]];
223+
}
224+
#endif
225+
218226
- (void)bindToView:(RNGHUIView *)view
219227
{
228+
self.recognizer.delegate = self;
229+
230+
#if RCT_NEW_ARCH_ENABLED
231+
// Starting from react-native 0.79 `RCTParagraphTextView` overrides `hitTest` method to return `nil`. This results in
232+
// native `UIGestureRecognizer` not responding to gestures. To fix this issue, we attach recognizer to its parent,
233+
// i.e. `RCTParagraphComponentView`.
234+
RNGHUIView *recognizerView = [self isViewParagraphComponent:view.superview] ? view.superview : view;
235+
#else
236+
RNGHUIView *recognizerView = view;
237+
#endif
238+
220239
#if !TARGET_OS_OSX
221-
view.userInteractionEnabled = YES;
240+
recognizerView.userInteractionEnabled = YES;
222241
#endif
223-
self.recognizer.delegate = self;
224-
[view addGestureRecognizer:self.recognizer];
225242

226-
[self bindManualActivationToView:view];
243+
[recognizerView addGestureRecognizer:self.recognizer];
244+
[self bindManualActivationToView:recognizerView];
227245
}
228246

229247
- (void)unbindFromView
@@ -249,12 +267,27 @@ - (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recogn
249267
#endif
250268
}
251269

270+
/**
271+
This method is used in `handleGesture` to choose appropriate view. `reactTag` in `RCTParagraphComponentView`
272+
is `nil`, therefore we want to use `reactTag` from `RCTParagraphTextView`.
273+
*/
274+
- (RNGHUIView *)chooseViewForInteraction:(UIGestureRecognizer *)recognizer
275+
{
276+
#if RCT_NEW_ARCH_ENABLED
277+
return [self isViewParagraphComponent:recognizer.view] ? recognizer.view.subviews[0] : recognizer.view;
278+
#else
279+
return recognizer.view;
280+
#endif
281+
}
282+
252283
- (void)handleGesture:(UIGestureRecognizer *)recognizer
253284
{
285+
RNGHUIView *view = [self chooseViewForInteraction:recognizer];
286+
254287
// it may happen that the gesture recognizer is reset after it's been unbound from the view,
255288
// it that recognizer tried to send event, the app would crash because the target of the event
256289
// would be nil.
257-
if (recognizer.view.reactTag == nil) {
290+
if (view.reactTag == nil) {
258291
return;
259292
}
260293

@@ -266,7 +299,9 @@ - (void)handleGesture:(UIGestureRecognizer *)recognizer inState:(RNGestureHandle
266299
{
267300
_state = state;
268301
RNGestureHandlerEventExtraData *eventData = [self eventExtraData:recognizer];
269-
[self sendEventsInState:self.state forViewWithTag:recognizer.view.reactTag withExtraData:eventData];
302+
RNGHUIView *view = [self chooseViewForInteraction:recognizer];
303+
304+
[self sendEventsInState:self.state forViewWithTag:view.reactTag withExtraData:eventData];
270305
}
271306

272307
- (void)sendEventsInState:(RNGestureHandlerState)state

0 commit comments

Comments
 (0)