#import "RNGestureHandler.h" #import "Handlers/RNNativeViewHandler.h" #import #import @interface UIGestureRecognizer (GestureHandler) @property (nonatomic, readonly) RNGestureHandler *gestureHandler; @end @implementation UIGestureRecognizer (GestureHandler) - (RNGestureHandler *)gestureHandler { id delegate = self.delegate; if ([delegate isKindOfClass:[RNGestureHandler class]]) { return (RNGestureHandler *)delegate; } return nil; } @end @implementation RNGestureHandler { NSArray *_handlersToWaitFor; NSArray *_simultaniousHandlers; UIEdgeInsets _hitSlopEdgeInsets; } - (instancetype)initWithTag:(NSNumber *)tag { if ((self = [super init])) { _tag = tag; _lastState = RNGestureHandlerStateUndetermined; _hitSlopEdgeInsets = UIEdgeInsetsZero; } return self; } - (void)configure:(NSDictionary *)config { _handlersToWaitFor = [RCTConvert NSNumberArray:config[@"waitFor"]]; _simultaniousHandlers = [RCTConvert NSNumberArray:config[@"simultaneousHandlers"]]; id prop = config[@"enabled"]; if (prop != nil) { self.enabled = [RCTConvert BOOL:prop]; } else { self.enabled = YES; } prop = config[@"shouldCancelWhenOutside"]; if (prop != nil) { _shouldCancelWhenOutside = [RCTConvert BOOL:prop]; } else { _shouldCancelWhenOutside = NO; } prop = config[@"hitSlop"]; if (prop != nil) { UIEdgeInsets insets = [RCTConvert UIEdgeInsets:prop]; _hitSlopEdgeInsets = UIEdgeInsetsMake(-insets.top, -insets.left, -insets.bottom, -insets.right); } } - (void)setEnabled:(BOOL)enabled { _enabled = enabled; self.recognizer.enabled = enabled; } - (void)bindToView:(UIView *)view { view.userInteractionEnabled = YES; self.recognizer.delegate = self; [view addGestureRecognizer:self.recognizer]; } - (void)unbindFromView { [self.recognizer.view removeGestureRecognizer:self.recognizer]; self.recognizer.delegate = nil; } - (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recognizer { return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:recognizer.view] withAbsolutePosition:[recognizer locationInView:recognizer.view.window]]; } - (void)handleGesture:(UIGestureRecognizer *)recognizer { RNGestureHandlerEventExtraData *eventData = [self eventExtraData:recognizer]; [self sendEventsInState:self.state forViewWithTag:recognizer.view.reactTag withExtraData:eventData]; } - (void)sendEventsInState:(RNGestureHandlerState)state forViewWithTag:(nonnull NSNumber *)reactTag withExtraData:(RNGestureHandlerEventExtraData *)extraData { id touchEvent = [[RNGestureHandlerEvent alloc] initWithRactTag:reactTag handlerTag:_tag state:state extraData:extraData]; if (state != _lastState) { if (state == RNGestureHandlerStateEnd && _lastState != RNGestureHandlerStateActive) { [self.emitter sendStateChangeEvent:[[RNGestureHandlerStateChange alloc] initWithRactTag:reactTag handlerTag:_tag state:RNGestureHandlerStateActive prevState:_lastState extraData:extraData]]; _lastState = RNGestureHandlerStateActive; } id stateEvent = [[RNGestureHandlerStateChange alloc] initWithRactTag:reactTag handlerTag:_tag state:state prevState:_lastState extraData:extraData]; [self.emitter sendStateChangeEvent:stateEvent]; _lastState = state; } if (state == RNGestureHandlerStateActive) { [self.emitter sendTouchEvent:touchEvent]; } } - (RNGestureHandlerState)state { switch (_recognizer.state) { case UIGestureRecognizerStateBegan: case UIGestureRecognizerStatePossible: return RNGestureHandlerStateBegan; case UIGestureRecognizerStateEnded: return RNGestureHandlerStateEnd; case UIGestureRecognizerStateFailed: return RNGestureHandlerStateFailed; case UIGestureRecognizerStateCancelled: return RNGestureHandlerStateCancelled; case UIGestureRecognizerStateChanged: return RNGestureHandlerStateActive; } return RNGestureHandlerStateUndetermined; } #pragma mark UIGestureRecognizerDelegate + (RNGestureHandler *)findGestureHandlerByRecognizer:(UIGestureRecognizer *)recognizer { RNGestureHandler *handler = recognizer.gestureHandler; if (handler != nil) { return handler; } // We may try to extract "DummyGestureHandler" in case when "otherGestureRecognizer" belongs to // a native view being wrapped with "NativeViewGestureHandler" UIView *reactView = recognizer.view; while (reactView != nil && reactView.reactTag == nil) { reactView = reactView.superview; } for (UIGestureRecognizer *recognizer in reactView.gestureRecognizers) { if ([recognizer isKindOfClass:[RNDummyGestureRecognizer class]]) { return recognizer.gestureHandler; } } return nil; } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer]; if ([handler isKindOfClass:[RNNativeViewGestureHandler class]]) { for (NSNumber *handlerTag in handler->_handlersToWaitFor) { if ([_tag isEqual:handlerTag]) { return YES; } } } return NO; } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { if ([_handlersToWaitFor count]) { RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer]; if (handler != nil) { for (NSNumber *handlerTag in _handlersToWaitFor) { if ([handler.tag isEqual:handlerTag]) { return YES; } } } } return NO; } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { if (_recognizer.state == UIGestureRecognizerStateBegan && _recognizer.state == UIGestureRecognizerStatePossible) { return YES; } if ([_simultaniousHandlers count]) { RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer]; if (handler != nil) { for (NSNumber *handlerTag in _simultaniousHandlers) { if ([handler.tag isEqual:handlerTag]) { return YES; } } } } return NO; } - (void)reset { _lastState = RNGestureHandlerStateUndetermined; } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { [self reset]; return YES; } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { // If hitSlop is set we use it to determine if a given gesture recognizer should start processing // touch stream. This only works for negative values of hitSlop as this method won't be triggered // unless touch startes in the bounds of the attached view. To acheve similar effect with positive // values of hitSlop one should set hitSlop for the underlying view. This limitation is due to the // fact that hitTest method is only available at the level of UIView if (!UIEdgeInsetsEqualToEdgeInsets(_hitSlopEdgeInsets, UIEdgeInsetsZero)) { CGPoint location = [touch locationInView:gestureRecognizer.view]; CGRect hitFrame = UIEdgeInsetsInsetRect(gestureRecognizer.view.bounds, _hitSlopEdgeInsets); return CGRectContainsPoint(hitFrame, location); } return YES; } @end