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