1#import "RNGestureHandlerManager.h"
2
3#import <React/RCTComponent.h>
4#import <React/RCTEventDispatcher.h>
5#import <React/RCTLog.h>
6#import <React/RCTModalHostViewController.h>
7#import <React/RCTRootContentView.h>
8#import <React/RCTRootView.h>
9#import <React/RCTUIManager.h>
10#import <React/RCTViewManager.h>
11
12#import "RNGestureHandler.h"
13#import "RNGestureHandlerActionType.h"
14#import "RNGestureHandlerRegistry.h"
15#import "RNGestureHandlerState.h"
16#import "RNRootViewGestureRecognizer.h"
17
18#ifdef RCT_NEW_ARCH_ENABLED
19#import <React/RCTFabricModalHostViewController.h>
20#import <React/RCTSurfaceTouchHandler.h>
21#import <React/RCTSurfaceView.h>
22#import <React/RCTViewComponentView.h>
23#else
24#import <React/RCTTouchHandler.h>
25#endif // RCT_NEW_ARCH_ENABLED
26
27#import "Handlers/RNFlingHandler.h"
28#import "Handlers/RNForceTouchHandler.h"
29#import "Handlers/RNLongPressHandler.h"
30#import "Handlers/RNManualHandler.h"
31#import "Handlers/RNNativeViewHandler.h"
32#import "Handlers/RNPanHandler.h"
33#import "Handlers/RNPinchHandler.h"
34#import "Handlers/RNRotationHandler.h"
35#import "Handlers/RNTapHandler.h"
36
37// We use the method below instead of RCTLog because we log out messages after the bridge gets
38// turned down in some cases. Which normally with RCTLog would cause a crash in DEBUG mode
39#define RCTLifecycleLog(...) \
40  RCTDefaultLogFunction(     \
41      RCTLogLevelInfo, RCTLogSourceNative, @(__FILE__), @(__LINE__), [NSString stringWithFormat:__VA_ARGS__])
42
43constexpr int NEW_ARCH_NUMBER_OF_ATTACH_RETRIES = 25;
44
45@interface RNGestureHandlerManager () <RNGestureHandlerEventEmitter, RNRootViewGestureRecognizerDelegate>
46
47@end
48
49@implementation RNGestureHandlerManager {
50  RNGestureHandlerRegistry *_registry;
51  RCTUIManager *_uiManager;
52  NSHashTable<RNRootViewGestureRecognizer *> *_rootViewGestureRecognizers;
53  NSMutableDictionary<NSNumber *, NSNumber *> *_attachRetryCounter;
54  RCTEventDispatcher *_eventDispatcher;
55  id _reanimatedModule;
56}
57
58- (instancetype)initWithUIManager:(RCTUIManager *)uiManager eventDispatcher:(RCTEventDispatcher *)eventDispatcher
59{
60  if ((self = [super init])) {
61    _uiManager = uiManager;
62    _eventDispatcher = eventDispatcher;
63    _registry = [RNGestureHandlerRegistry new];
64    _rootViewGestureRecognizers = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
65    _attachRetryCounter = [[NSMutableDictionary alloc] init];
66    _reanimatedModule = nil;
67  }
68  return self;
69}
70
71- (void)createGestureHandler:(NSString *)handlerName tag:(NSNumber *)handlerTag config:(NSDictionary *)config
72{
73  static NSDictionary *map;
74  static dispatch_once_t mapToken;
75  dispatch_once(&mapToken, ^{
76    map = @{
77      @"PanGestureHandler" : [RNPanGestureHandler class],
78      @"TapGestureHandler" : [RNTapGestureHandler class],
79      @"FlingGestureHandler" : [RNFlingGestureHandler class],
80      @"LongPressGestureHandler" : [RNLongPressGestureHandler class],
81      @"NativeViewGestureHandler" : [RNNativeViewGestureHandler class],
82      @"PinchGestureHandler" : [RNPinchGestureHandler class],
83      @"RotationGestureHandler" : [RNRotationGestureHandler class],
84      @"ForceTouchGestureHandler" : [RNForceTouchHandler class],
85      @"ManualGestureHandler" : [RNManualGestureHandler class],
86    };
87  });
88
89  Class nodeClass = map[handlerName];
90  if (!nodeClass) {
91    RCTLogError(@"Gesture handler type %@ is not supported", handlerName);
92    return;
93  }
94
95  RNGestureHandler *gestureHandler = [[nodeClass alloc] initWithTag:handlerTag];
96  [gestureHandler configure:config];
97  [_registry registerGestureHandler:gestureHandler];
98
99  __weak id<RNGestureHandlerEventEmitter> emitter = self;
100  gestureHandler.emitter = emitter;
101}
102
103- (void)attachGestureHandler:(nonnull NSNumber *)handlerTag
104               toViewWithTag:(nonnull NSNumber *)viewTag
105              withActionType:(RNGestureHandlerActionType)actionType
106{
107  UIView *view = [_uiManager viewForReactTag:viewTag];
108
109#ifdef RCT_NEW_ARCH_ENABLED
110  if (view == nil || view.superview == nil) {
111    // There are a few reasons we could end up here:
112    // - the native view corresponding to the viewtag hasn't yet been created
113    // - the native view has been created, but it's not attached to window
114    // - the native view will not exist because it got flattened
115    // In the first two cases we just want to wait until the view gets created or gets attached to its superview
116    // In the third case we don't want to do anything but we cannot easily distinguish it here, hece the abomination
117    // below
118    // TODO: would be great to have a better solution, although it might require migration to the shadow nodes from
119    // viewTags
120
121    NSNumber *counter = [_attachRetryCounter objectForKey:viewTag];
122    if (counter == nil) {
123      counter = @1;
124    } else {
125      counter = [NSNumber numberWithInt:counter.intValue + 1];
126    }
127
128    if (counter.intValue > NEW_ARCH_NUMBER_OF_ATTACH_RETRIES) {
129      [_attachRetryCounter removeObjectForKey:viewTag];
130    } else {
131      [_attachRetryCounter setObject:counter forKey:viewTag];
132
133      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
134        [self attachGestureHandler:handlerTag toViewWithTag:viewTag withActionType:actionType];
135      });
136    }
137
138    return;
139  }
140
141  [_attachRetryCounter removeObjectForKey:viewTag];
142
143  // I think it should be moved to RNNativeViewHandler, but that would require
144  // additional logic for setting contentView.reactTag, this works for now
145  if ([view isKindOfClass:[RCTViewComponentView class]]) {
146    RCTViewComponentView *componentView = (RCTViewComponentView *)view;
147    if (componentView.contentView != nil) {
148      view = componentView.contentView;
149    }
150  }
151
152  view.reactTag = viewTag; // necessary for RNReanimated eventHash (e.g. "42onGestureHandlerEvent"), also will be
153                           // returned as event.target
154#endif // RCT_NEW_ARCH_ENABLED
155
156  [_registry attachHandlerWithTag:handlerTag toView:view withActionType:actionType];
157
158  // register view if not already there
159  [self registerViewWithGestureRecognizerAttachedIfNeeded:view];
160}
161
162- (void)updateGestureHandler:(NSNumber *)handlerTag config:(NSDictionary *)config
163{
164  RNGestureHandler *handler = [_registry handlerWithTag:handlerTag];
165  [handler configure:config];
166}
167
168- (void)dropGestureHandler:(NSNumber *)handlerTag
169{
170  [_registry dropHandlerWithTag:handlerTag];
171}
172
173- (void)dropAllGestureHandlers
174{
175  [_registry dropAllHandlers];
176}
177
178- (void)handleSetJSResponder:(NSNumber *)viewTag blockNativeResponder:(NSNumber *)blockNativeResponder
179{
180  if ([blockNativeResponder boolValue]) {
181    for (RNRootViewGestureRecognizer *recognizer in _rootViewGestureRecognizers) {
182      [recognizer blockOtherRecognizers];
183    }
184  }
185}
186
187- (void)handleClearJSResponder
188{
189  // ignore...
190}
191
192- (id)handlerWithTag:(NSNumber *)handlerTag
193{
194  return [_registry handlerWithTag:handlerTag];
195}
196
197#pragma mark Root Views Management
198
199- (void)registerViewWithGestureRecognizerAttachedIfNeeded:(UIView *)childView
200{
201#ifdef RCT_NEW_ARCH_ENABLED
202  UIView *touchHandlerView = childView;
203
204  if ([[childView reactViewController] isKindOfClass:[RCTFabricModalHostViewController class]]) {
205    touchHandlerView = [childView reactViewController].view;
206  } else {
207    while (touchHandlerView != nil && ![touchHandlerView isKindOfClass:[RCTSurfaceView class]]) {
208      touchHandlerView = touchHandlerView.superview;
209    }
210  }
211#else
212  UIView *touchHandlerView = nil;
213
214  if ([[childView reactViewController] isKindOfClass:[RCTModalHostViewController class]]) {
215    touchHandlerView = [childView reactViewController].view.subviews[0];
216  } else {
217    UIView *parent = childView;
218    while (parent != nil && ![parent respondsToSelector:@selector(touchHandler)]) {
219      parent = parent.superview;
220    }
221
222    touchHandlerView = [[parent performSelector:@selector(touchHandler)] view];
223  }
224#endif // RCT_NEW_ARCH_ENABLED
225
226  if (touchHandlerView == nil) {
227    return;
228  }
229
230  // Many views can return the same touchHandler so we check if the one we want to register
231  // is not already present in the set.
232  for (UIGestureRecognizer *recognizer in touchHandlerView.gestureRecognizers) {
233    if ([recognizer isKindOfClass:[RNRootViewGestureRecognizer class]]) {
234      return;
235    }
236  }
237
238  RCTLifecycleLog(@"[GESTURE HANDLER] Initialize gesture handler for view %@", touchHandlerView);
239  RNRootViewGestureRecognizer *recognizer = [RNRootViewGestureRecognizer new];
240  recognizer.delegate = self;
241  touchHandlerView.userInteractionEnabled = YES;
242  [touchHandlerView addGestureRecognizer:recognizer];
243  [_rootViewGestureRecognizers addObject:recognizer];
244}
245
246- (void)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
247    didActivateInViewWithTouchHandler:(UIView *)viewWithTouchHandler
248{
249  // Cancel touches in RN's root view in order to cancel all in-js recognizers
250
251  // As scroll events are special-cased in RN responder implementation and sending them would
252  // trigger JS responder change, we don't cancel touches if the handler that got activated is
253  // a scroll recognizer. This way root view will keep sending touchMove and touchEnd events
254  // and therefore allow JS responder to properly release the responder at the end of the touch
255  // stream.
256  // NOTE: this is not a proper fix and solving this problem requires upstream fixes to RN. In
257  // particular if we have one PanHandler and ScrollView that can work simultaniously then when
258  // the Pan handler activates it would still tigger cancel events.
259  // Once the upstream fix lands the line below along with this comment can be removed
260  if ([gestureRecognizer.view isKindOfClass:[UIScrollView class]])
261    return;
262
263  UIGestureRecognizer *touchHandler = nil;
264
265  // this way we can extract the touch handler on both architectures relatively easily
266  for (UIGestureRecognizer *recognizer in [viewWithTouchHandler gestureRecognizers]) {
267#ifdef RCT_NEW_ARCH_ENABLED
268    if ([recognizer isKindOfClass:[RCTSurfaceTouchHandler class]]) {
269#else
270    if ([recognizer isKindOfClass:[RCTTouchHandler class]]) {
271#endif // RCT_NEW_ARCH_ENABLED
272      touchHandler = recognizer;
273      break;
274    }
275  }
276  [touchHandler setEnabled:NO];
277  [touchHandler setEnabled:YES];
278}
279
280#pragma mark Events
281
282- (void)sendEvent:(RNGestureHandlerStateChange *)event withActionType:(RNGestureHandlerActionType)actionType
283{
284  switch (actionType) {
285    case RNGestureHandlerActionTypeReanimatedWorklet:
286      [self sendEventForReanimated:event];
287      break;
288
289    case RNGestureHandlerActionTypeNativeAnimatedEvent:
290      if ([event.eventName isEqualToString:@"onGestureHandlerEvent"]) {
291        [self sendEventForNativeAnimatedEvent:event];
292      } else {
293        // Although onGestureEvent prop is an Animated.event with useNativeDriver: true,
294        // onHandlerStateChange prop is still a regular JS function.
295        // Also, Animated.event is only supported with old API.
296        [self sendEventForJSFunctionOldAPI:event];
297      }
298      break;
299
300    case RNGestureHandlerActionTypeJSFunctionOldAPI:
301      [self sendEventForJSFunctionOldAPI:event];
302      break;
303
304    case RNGestureHandlerActionTypeJSFunctionNewAPI:
305      [self sendEventForJSFunctionNewAPI:event];
306      break;
307  }
308}
309
310- (void)sendEventForReanimated:(RNGestureHandlerStateChange *)event
311{
312  // Delivers the event to Reanimated.
313#ifdef RCT_NEW_ARCH_ENABLED
314  // Send event directly to Reanimated
315  if (_reanimatedModule == nil) {
316    _reanimatedModule = [_uiManager.bridge moduleForName:@"ReanimatedModule"];
317  }
318
319  [_reanimatedModule eventDispatcherWillDispatchEvent:event];
320#else
321  // In the old architecture, Reanimated overwrites RCTEventDispatcher
322  // with REAEventDispatcher and intercepts all direct events.
323  [self sendEventForDirectEvent:event];
324#endif // RCT_NEW_ARCH_ENABLED
325}
326
327- (void)sendEventForNativeAnimatedEvent:(RNGestureHandlerStateChange *)event
328{
329  // Delivers the event to NativeAnimatedModule.
330  // Currently, NativeAnimated[Turbo]Module is RCTEventDispatcherObserver so we can
331  // simply send a direct event which is handled by the observer but ignored on JS side.
332  // TODO: send event directly to NativeAnimated[Turbo]Module
333  [self sendEventForDirectEvent:event];
334}
335
336- (void)sendEventForJSFunctionOldAPI:(RNGestureHandlerStateChange *)event
337{
338  // Delivers the event to JS (old RNGH API).
339#ifdef RCT_NEW_ARCH_ENABLED
340  [self sendEventForDeviceEvent:event];
341#else
342  [self sendEventForDirectEvent:event];
343#endif // RCT_NEW_ARCH_ENABLED
344}
345
346- (void)sendEventForJSFunctionNewAPI:(RNGestureHandlerStateChange *)event
347{
348  // Delivers the event to JS (new RNGH API).
349  [self sendEventForDeviceEvent:event];
350}
351
352- (void)sendEventForDirectEvent:(RNGestureHandlerStateChange *)event
353{
354  // Delivers the event to JS as a direct event.
355  [_eventDispatcher sendEvent:event];
356}
357
358- (void)sendEventForDeviceEvent:(RNGestureHandlerStateChange *)event
359{
360  // Delivers the event to JS as a device event.
361  NSMutableDictionary *body = [[event arguments] objectAtIndex:2];
362  [_eventDispatcher sendDeviceEventWithName:@"onGestureHandlerStateChange" body:body];
363}
364
365@end
366