You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

305 lines
10 KiB

#import "RNGestureHandlerManager.h"
#import <React/RCTComponent.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTLog.h>
#import <React/RCTRootContentView.h>
#import <React/RCTRootView.h>
#import <React/RCTUIManager.h>
#import <React/RCTViewManager.h>
#import "RNGestureHandler.h"
#import "RNGestureHandlerActionType.h"
#import "RNGestureHandlerRegistry.h"
#import "RNGestureHandlerState.h"
#import "RNRootViewGestureRecognizer.h"
#ifdef RN_FABRIC_ENABLED
#import <React/RCTSurfaceTouchHandler.h>
#import <React/RCTViewComponentView.h>
#else
#import <React/RCTTouchHandler.h>
#endif // RN_FABRIC_ENABLED
#import "Handlers/RNFlingHandler.h"
#import "Handlers/RNForceTouchHandler.h"
#import "Handlers/RNLongPressHandler.h"
#import "Handlers/RNManualHandler.h"
#import "Handlers/RNNativeViewHandler.h"
#import "Handlers/RNPanHandler.h"
#import "Handlers/RNPinchHandler.h"
#import "Handlers/RNRotationHandler.h"
#import "Handlers/RNTapHandler.h"
// We use the method below instead of RCTLog because we log out messages after the bridge gets
// turned down in some cases. Which normally with RCTLog would cause a crash in DEBUG mode
#define RCTLifecycleLog(...) \
RCTDefaultLogFunction( \
RCTLogLevelInfo, RCTLogSourceNative, @(__FILE__), @(__LINE__), [NSString stringWithFormat:__VA_ARGS__])
@interface RNGestureHandlerManager () <RNGestureHandlerEventEmitter, RNRootViewGestureRecognizerDelegate>
@end
@implementation RNGestureHandlerManager {
RNGestureHandlerRegistry *_registry;
RCTUIManager *_uiManager;
NSHashTable<RNRootViewGestureRecognizer *> *_rootViewGestureRecognizers;
RCTEventDispatcher *_eventDispatcher;
id _reanimatedModule;
}
- (instancetype)initWithUIManager:(RCTUIManager *)uiManager eventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super init])) {
_uiManager = uiManager;
_eventDispatcher = eventDispatcher;
_registry = [RNGestureHandlerRegistry new];
_rootViewGestureRecognizers = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
_reanimatedModule = nil;
}
return self;
}
- (void)createGestureHandler:(NSString *)handlerName tag:(NSNumber *)handlerTag config:(NSDictionary *)config
{
static NSDictionary *map;
static dispatch_once_t mapToken;
dispatch_once(&mapToken, ^{
map = @{
@"PanGestureHandler" : [RNPanGestureHandler class],
@"TapGestureHandler" : [RNTapGestureHandler class],
@"FlingGestureHandler" : [RNFlingGestureHandler class],
@"LongPressGestureHandler" : [RNLongPressGestureHandler class],
@"NativeViewGestureHandler" : [RNNativeViewGestureHandler class],
@"PinchGestureHandler" : [RNPinchGestureHandler class],
@"RotationGestureHandler" : [RNRotationGestureHandler class],
@"ForceTouchGestureHandler" : [RNForceTouchHandler class],
@"ManualGestureHandler" : [RNManualGestureHandler class],
};
});
Class nodeClass = map[handlerName];
if (!nodeClass) {
RCTLogError(@"Gesture handler type %@ is not supported", handlerName);
return;
}
RNGestureHandler *gestureHandler = [[nodeClass alloc] initWithTag:handlerTag];
[gestureHandler configure:config];
[_registry registerGestureHandler:gestureHandler];
__weak id<RNGestureHandlerEventEmitter> emitter = self;
gestureHandler.emitter = emitter;
}
- (void)attachGestureHandler:(nonnull NSNumber *)handlerTag
toViewWithTag:(nonnull NSNumber *)viewTag
withActionType:(RNGestureHandlerActionType)actionType
{
UIView *view = [_uiManager viewForReactTag:viewTag];
#ifdef RN_FABRIC_ENABLED
if (view == nil) {
// Happens when the view with given tag has been flattened.
// We cannot attach gesture handler to a non-existent view.
return;
}
// I think it should be moved to RNNativeViewHandler, but that would require
// additional logic for setting contentView.reactTag, this works for now
if ([view isKindOfClass:[RCTViewComponentView class]]) {
RCTViewComponentView *componentView = (RCTViewComponentView *)view;
if (componentView.contentView != nil) {
view = componentView.contentView;
}
}
view.reactTag = viewTag; // necessary for RNReanimated eventHash (e.g. "42onGestureHandlerEvent"), also will be
// returned as event.target
#endif // RN_FABRIC_ENABLED
[_registry attachHandlerWithTag:handlerTag toView:view withActionType:actionType];
// register view if not already there
[self registerViewWithGestureRecognizerAttachedIfNeeded:view];
}
- (void)updateGestureHandler:(NSNumber *)handlerTag config:(NSDictionary *)config
{
RNGestureHandler *handler = [_registry handlerWithTag:handlerTag];
[handler configure:config];
}
- (void)dropGestureHandler:(NSNumber *)handlerTag
{
[_registry dropHandlerWithTag:handlerTag];
}
- (void)dropAllGestureHandlers
{
[_registry dropAllHandlers];
}
- (void)handleSetJSResponder:(NSNumber *)viewTag blockNativeResponder:(NSNumber *)blockNativeResponder
{
if ([blockNativeResponder boolValue]) {
for (RNRootViewGestureRecognizer *recognizer in _rootViewGestureRecognizers) {
[recognizer blockOtherRecognizers];
}
}
}
- (void)handleClearJSResponder
{
// ignore...
}
- (id)handlerWithTag:(NSNumber *)handlerTag
{
return [_registry handlerWithTag:handlerTag];
}
#pragma mark Root Views Management
- (void)registerViewWithGestureRecognizerAttachedIfNeeded:(UIView *)childView
{
UIView *parent = childView;
while (parent != nil && ![parent respondsToSelector:@selector(touchHandler)])
parent = parent.superview;
// Many views can return the same touchHandler so we check if the one we want to register
// is not already present in the set.
UIView *touchHandlerView = [[parent performSelector:@selector(touchHandler)] view];
if (touchHandlerView == nil) {
return;
}
for (UIGestureRecognizer *recognizer in touchHandlerView.gestureRecognizers) {
if ([recognizer isKindOfClass:[RNRootViewGestureRecognizer class]]) {
return;
}
}
RCTLifecycleLog(@"[GESTURE HANDLER] Initialize gesture handler for view %@", touchHandlerView);
RNRootViewGestureRecognizer *recognizer = [RNRootViewGestureRecognizer new];
recognizer.delegate = self;
touchHandlerView.userInteractionEnabled = YES;
[touchHandlerView addGestureRecognizer:recognizer];
[_rootViewGestureRecognizers addObject:recognizer];
}
- (void)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
didActivateInViewWithTouchHandler:(UIView *)viewWithTouchHandler
{
// Cancel touches in RN's root view in order to cancel all in-js recognizers
// As scroll events are special-cased in RN responder implementation and sending them would
// trigger JS responder change, we don't cancel touches if the handler that got activated is
// a scroll recognizer. This way root view will keep sending touchMove and touchEnd events
// and therefore allow JS responder to properly release the responder at the end of the touch
// stream.
// NOTE: this is not a proper fix and solving this problem requires upstream fixes to RN. In
// particular if we have one PanHandler and ScrollView that can work simultaniously then when
// the Pan handler activates it would still tigger cancel events.
// Once the upstream fix lands the line below along with this comment can be removed
if ([gestureRecognizer.view isKindOfClass:[UIScrollView class]])
return;
#ifdef RN_FABRIC_ENABLED
RCTSurfaceTouchHandler *touchHandler = [viewWithTouchHandler performSelector:@selector(touchHandler)];
#else
RCTTouchHandler *touchHandler = [viewWithTouchHandler performSelector:@selector(touchHandler)];
#endif
[touchHandler setEnabled:NO];
[touchHandler setEnabled:YES];
}
#pragma mark Events
- (void)sendEvent:(RNGestureHandlerStateChange *)event withActionType:(RNGestureHandlerActionType)actionType
{
switch (actionType) {
case RNGestureHandlerActionTypeReanimatedWorklet:
[self sendEventForReanimated:event];
break;
case RNGestureHandlerActionTypeNativeAnimatedEvent:
if ([event.eventName isEqualToString:@"onGestureHandlerEvent"]) {
[self sendEventForNativeAnimatedEvent:event];
} else {
// Although onGestureEvent prop is an Animated.event with useNativeDriver: true,
// onHandlerStateChange prop is still a regular JS function.
// Also, Animated.event is only supported with old API.
[self sendEventForJSFunctionOldAPI:event];
}
break;
case RNGestureHandlerActionTypeJSFunctionOldAPI:
[self sendEventForJSFunctionOldAPI:event];
break;
case RNGestureHandlerActionTypeJSFunctionNewAPI:
[self sendEventForJSFunctionNewAPI:event];
break;
}
}
- (void)sendEventForReanimated:(RNGestureHandlerStateChange *)event
{
// Delivers the event to Reanimated.
#ifdef RN_FABRIC_ENABLED
// Send event directly to Reanimated
if (_reanimatedModule == nil) {
_reanimatedModule = [_uiManager.bridge moduleForName:@"ReanimatedModule"];
}
[_reanimatedModule eventDispatcherWillDispatchEvent:event];
#else
// In the old architecture, Reanimated overwrites RCTEventDispatcher
// with REAEventDispatcher and intercepts all direct events.
[self sendEventForDirectEvent:event];
#endif // RN_FABRIC_ENABLED
}
- (void)sendEventForNativeAnimatedEvent:(RNGestureHandlerStateChange *)event
{
// Delivers the event to NativeAnimatedModule.
// Currently, NativeAnimated[Turbo]Module is RCTEventDispatcherObserver so we can
// simply send a direct event which is handled by the observer but ignored on JS side.
// TODO: send event directly to NativeAnimated[Turbo]Module
[self sendEventForDirectEvent:event];
}
- (void)sendEventForJSFunctionOldAPI:(RNGestureHandlerStateChange *)event
{
// Delivers the event to JS (old RNGH API).
#ifdef RN_FABRIC_ENABLED
[self sendEventForDeviceEvent:event];
#else
[self sendEventForDirectEvent:event];
#endif // RN_FABRIC_ENABLED
}
- (void)sendEventForJSFunctionNewAPI:(RNGestureHandlerStateChange *)event
{
// Delivers the event to JS (new RNGH API).
[self sendEventForDeviceEvent:event];
}
- (void)sendEventForDirectEvent:(RNGestureHandlerStateChange *)event
{
// Delivers the event to JS as a direct event.
[_eventDispatcher sendEvent:event];
}
- (void)sendEventForDeviceEvent:(RNGestureHandlerStateChange *)event
{
// Delivers the event to JS as a device event.
NSMutableDictionary *body = [[event arguments] objectAtIndex:2];
[_eventDispatcher sendDeviceEventWithName:@"onGestureHandlerStateChange" body:body];
}
@end