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
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
|