diff --git a/android/lib/src/main/java/com/swmansion/gesturehandler/GestureHandler.kt b/android/lib/src/main/java/com/swmansion/gesturehandler/GestureHandler.kt index f690e42efb..33c0a9cc24 100644 --- a/android/lib/src/main/java/com/swmansion/gesturehandler/GestureHandler.kt +++ b/android/lib/src/main/java/com/swmansion/gesturehandler/GestureHandler.kt @@ -46,6 +46,7 @@ open class GestureHandler = Array(MAX_POINTERS_COUNT) { null } + private var deferredPointerEventSender: (() -> Unit)? = null var needsPointerData = false @@ -447,14 +448,36 @@ open class GestureHandler { + dispatchTouchDownEvent(event) + dispatchTouchMoveEvent(event) + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> { + dispatchTouchMoveEvent(event) + dispatchTouchUpEvent(event) + } + MotionEvent.ACTION_MOVE -> { + dispatchTouchMoveEvent(event) + } + } + } + + if (this.state != STATE_UNDETERMINED) { + eventSender() + } else { + // this is the first event, we want to defer it + this.deferredPointerEventSender = eventSender } } @@ -648,6 +671,19 @@ open class GestureHandler when (newState) { - GestureHandler.STATE_ACTIVE -> handler.activate(force = true) + GestureHandler.STATE_ACTIVE -> { + // also call begin to force correct state flow in case the `activate` is called in the + // UNDETERMINED state + handler.begin() + handler.activate(force = true) + } GestureHandler.STATE_BEGAN -> handler.begin() GestureHandler.STATE_END -> handler.end() GestureHandler.STATE_FAILED -> handler.fail() diff --git a/ios/Handlers/RNFlingHandler.m b/ios/Handlers/RNFlingHandler.m index d01f8d7b97..7ec268b106 100644 --- a/ios/Handlers/RNFlingHandler.m +++ b/ios/Handlers/RNFlingHandler.m @@ -2,12 +2,12 @@ @interface RNBetterSwipeGestureRecognizer : UISwipeGestureRecognizer -- (id)initWithGestureHandler:(RNGestureHandler*)gestureHandler; +- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler; @end @implementation RNBetterSwipeGestureRecognizer { - __weak RNGestureHandler* _gestureHandler; + __weak RNGestureHandler *_gestureHandler; CGPoint _lastPoint; // location of the most recently updated touch, relative to the view bool _hasBegan; // whether the `BEGAN` event has been sent } @@ -27,6 +27,7 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event _lastPoint = [[[touches allObjects] objectAtIndex:0] locationInView:_gestureHandler.recognizer.view]; [_gestureHandler reset]; [super touchesBegan:touches withEvent:event]; + [_gestureHandler.pointerTracker touchesBegan:touches withEvent:event]; // self.numberOfTouches doesn't work for this because in case than one finger is required, // when holding one finger on the screen and tapping with the second one, numberOfTouches is equal @@ -35,8 +36,6 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event [self triggerAction]; _hasBegan = YES; } - - [_gestureHandler.pointerTracker touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event @@ -73,7 +72,8 @@ - (void)reset [super reset]; } -- (CGPoint)getLastLocation { +- (CGPoint)getLastLocation +{ // I think keeping the location of only one touch is enough since it would be used to determine the direction // of the movement, and if it's wrong the recognizer fails anyway. // In case the location of all touches is required, touch events are the way to go @@ -103,48 +103,50 @@ - (void)resetConfig - (void)configure:(NSDictionary *)config { - [super configure:config]; - UISwipeGestureRecognizer *recognizer = (UISwipeGestureRecognizer *)_recognizer; - - id prop = config[@"direction"]; - if (prop != nil) { - recognizer.direction = [RCTConvert NSInteger:prop]; - } - + [super configure:config]; + UISwipeGestureRecognizer *recognizer = (UISwipeGestureRecognizer *)_recognizer; + + id prop = config[@"direction"]; + if (prop != nil) { + recognizer.direction = [RCTConvert NSInteger:prop]; + } + #if !TARGET_OS_TV - prop = config[@"numberOfPointers"]; - if (prop != nil) { - recognizer.numberOfTouchesRequired = [RCTConvert NSInteger:prop]; - } + prop = config[@"numberOfPointers"]; + if (prop != nil) { + recognizer.numberOfTouchesRequired = [RCTConvert NSInteger:prop]; + } #endif } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { - RNGestureHandlerState savedState = _lastState; - BOOL shouldBegin = [super gestureRecognizerShouldBegin:gestureRecognizer]; - _lastState = savedState; - - return shouldBegin; + RNGestureHandlerState savedState = _lastState; + BOOL shouldBegin = [super gestureRecognizerShouldBegin:gestureRecognizer]; + _lastState = savedState; + + return shouldBegin; } - (RNGestureHandlerEventExtraData *)eventExtraData:(id)_recognizer { - // For some weird reason [recognizer locationInView:recognizer.view.window] returns (0, 0). - // To calculate the correct absolute position, first calculate the absolute position of the - // view inside the root view controller (https://stackoverflow.com/a/7448573) and then - // add the relative touch position to it. - - RNBetterSwipeGestureRecognizer *recognizer = (RNBetterSwipeGestureRecognizer *)_recognizer; - - CGPoint viewAbsolutePosition = [recognizer.view convertPoint:recognizer.view.bounds.origin toView:[UIApplication sharedApplication].keyWindow.rootViewController.view]; - CGPoint locationInView = [recognizer getLastLocation]; - - return [RNGestureHandlerEventExtraData - forPosition:locationInView - withAbsolutePosition:CGPointMake(viewAbsolutePosition.x + locationInView.x, viewAbsolutePosition.y + locationInView.y) - withNumberOfTouches:recognizer.numberOfTouches]; + // For some weird reason [recognizer locationInView:recognizer.view.window] returns (0, 0). + // To calculate the correct absolute position, first calculate the absolute position of the + // view inside the root view controller (https://stackoverflow.com/a/7448573) and then + // add the relative touch position to it. + + RNBetterSwipeGestureRecognizer *recognizer = (RNBetterSwipeGestureRecognizer *)_recognizer; + + CGPoint viewAbsolutePosition = + [recognizer.view convertPoint:recognizer.view.bounds.origin + toView:[UIApplication sharedApplication].keyWindow.rootViewController.view]; + CGPoint locationInView = [recognizer getLastLocation]; + + return [RNGestureHandlerEventExtraData + forPosition:locationInView + withAbsolutePosition:CGPointMake( + viewAbsolutePosition.x + locationInView.x, viewAbsolutePosition.y + locationInView.y) + withNumberOfTouches:recognizer.numberOfTouches]; } @end - diff --git a/ios/Handlers/RNPanHandler.m b/ios/Handlers/RNPanHandler.m index 3269d3d3d0..07bdf0a518 100644 --- a/ios/Handlers/RNPanHandler.m +++ b/ios/Handlers/RNPanHandler.m @@ -26,19 +26,17 @@ @interface RNBetterPanGestureRecognizer : UIPanGestureRecognizer @property (nonatomic) CGFloat failOffsetYEnd; @property (nonatomic) CGFloat activateAfterLongPress; - -- (id)initWithGestureHandler:(RNGestureHandler*)gestureHandler; +- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler; @end - @implementation RNBetterPanGestureRecognizer { __weak RNGestureHandler *_gestureHandler; NSUInteger _realMinimumNumberOfTouches; BOOL _hasCustomActivationCriteria; } -- (id)initWithGestureHandler:(RNGestureHandler*)gestureHandler +- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler { if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) { _gestureHandler = gestureHandler; @@ -96,9 +94,9 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event } #endif [super touchesBegan:touches withEvent:event]; - [self triggerAction]; [_gestureHandler.pointerTracker touchesBegan:touches withEvent:event]; - + [self triggerAction]; + if (!isnan(_activateAfterLongPress)) { [self performSelector:@selector(activateAfterLongPress) withObject:nil afterDelay:_activateAfterLongPress]; } @@ -108,7 +106,7 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesMoved:touches withEvent:event]; [_gestureHandler.pointerTracker touchesMoved:touches withEvent:event]; - + if (self.state == UIGestureRecognizerStatePossible && [self shouldFailUnderCustomCriteria]) { self.state = UIGestureRecognizerStateFailed; return; @@ -119,14 +117,14 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event // then UIGestureRecognizer's sate machine will only transition to // UIGestureRecognizerStateCancelled even if you set the state to // UIGestureRecognizerStateFailed here. Making the behavior explicit. - self.state = (self.state == UIGestureRecognizerStatePossible) - ? UIGestureRecognizerStateFailed - : UIGestureRecognizerStateCancelled; + self.state = (self.state == UIGestureRecognizerStatePossible) ? UIGestureRecognizerStateFailed + : UIGestureRecognizerStateCancelled; [self reset]; return; } } - if (_hasCustomActivationCriteria && self.state == UIGestureRecognizerStatePossible && [self shouldActivateUnderCustomCriteria]) { + if (_hasCustomActivationCriteria && self.state == UIGestureRecognizerStatePossible && + [self shouldActivateUnderCustomCriteria]) { #if !TARGET_OS_TV super.minimumNumberOfTouches = _realMinimumNumberOfTouches; if ([self numberOfTouches] >= _realMinimumNumberOfTouches) { @@ -160,10 +158,9 @@ - (void)reset - (void)updateHasCustomActivationCriteria { - _hasCustomActivationCriteria = !isnan(_minDistSq) - || !isnan(_minVelocityX) || !isnan(_minVelocityY) || !isnan(_minVelocitySq) - || !isnan(_activeOffsetXStart) || !isnan(_activeOffsetXEnd) - || !isnan(_activeOffsetYStart) || !isnan(_activeOffsetYEnd); + _hasCustomActivationCriteria = !isnan(_minDistSq) || !isnan(_minVelocityX) || !isnan(_minVelocityY) || + !isnan(_minVelocitySq) || !isnan(_activeOffsetXStart) || !isnan(_activeOffsetXEnd) || + !isnan(_activeOffsetYStart) || !isnan(_activeOffsetYEnd); } - (BOOL)shouldFailUnderCustomCriteria @@ -174,7 +171,7 @@ - (BOOL)shouldFailUnderCustomCriteria [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(activateAfterLongPress) object:nil]; return YES; } - + if (!isnan(_failOffsetXStart) && trans.x < _failOffsetXStart) { return YES; } @@ -205,11 +202,11 @@ - (BOOL)shouldActivateUnderCustomCriteria if (!isnan(_activeOffsetYEnd) && trans.y > _activeOffsetYEnd) { return YES; } - + if (TEST_MIN_IF_NOT_NAN(VEC_LEN_SQ(trans), _minDistSq)) { return YES; } - + CGPoint velocity = [self velocityInView:self.view]; if (TEST_MIN_IF_NOT_NAN(velocity.x, _minVelocityX)) { return YES; @@ -220,7 +217,7 @@ - (BOOL)shouldActivateUnderCustomCriteria if (TEST_MIN_IF_NOT_NAN(VEC_LEN_SQ(velocity), _minVelocitySq)) { return YES; } - + return NO; } @@ -269,7 +266,7 @@ - (void)configure:(NSDictionary *)config { [super configure:config]; RNBetterPanGestureRecognizer *recognizer = (RNBetterPanGestureRecognizer *)_recognizer; - + APPLY_FLOAT_PROP(minVelocityX); APPLY_FLOAT_PROP(minVelocityY); APPLY_FLOAT_PROP(activeOffsetXStart); @@ -284,7 +281,7 @@ - (void)configure:(NSDictionary *)config #if !TARGET_OS_TV && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130400 if (@available(iOS 13.4, *)) { bool enableTrackpadTwoFingerGesture = [RCTConvert BOOL:config[@"enableTrackpadTwoFingerGesture"]]; - if(enableTrackpadTwoFingerGesture){ + if (enableTrackpadTwoFingerGesture) { recognizer.allowedScrollTypesMask = UIScrollTypeMaskAll; } } @@ -292,19 +289,19 @@ - (void)configure:(NSDictionary *)config APPLY_NAMED_INT_PROP(minimumNumberOfTouches, @"minPointers"); APPLY_NAMED_INT_PROP(maximumNumberOfTouches, @"maxPointers"); #endif - + id prop = config[@"minDist"]; if (prop != nil) { CGFloat dist = [RCTConvert CGFloat:prop]; recognizer.minDistSq = dist * dist; } - + prop = config[@"minVelocity"]; if (prop != nil) { CGFloat velocity = [RCTConvert CGFloat:prop]; recognizer.minVelocitySq = velocity * velocity; } - + prop = config[@"activateAfterLongPress"]; if (prop != nil) { recognizer.activateAfterLongPress = [RCTConvert CGFloat:prop] / 1000.0; @@ -313,23 +310,22 @@ - (void)configure:(NSDictionary *)config [recognizer updateHasCustomActivationCriteria]; } -- (BOOL) gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { RNGestureHandlerState savedState = _lastState; BOOL shouldBegin = [super gestureRecognizerShouldBegin:gestureRecognizer]; _lastState = savedState; - + return shouldBegin; } - (RNGestureHandlerEventExtraData *)eventExtraData:(UIPanGestureRecognizer *)recognizer { - return [RNGestureHandlerEventExtraData - forPan:[recognizer locationInView:recognizer.view] - withAbsolutePosition:[recognizer locationInView:recognizer.view.window] - withTranslation:[recognizer translationInView:recognizer.view.window] - withVelocity:[recognizer velocityInView:recognizer.view.window] - withNumberOfTouches:recognizer.numberOfTouches]; + return [RNGestureHandlerEventExtraData forPan:[recognizer locationInView:recognizer.view] + withAbsolutePosition:[recognizer locationInView:recognizer.view.window] + withTranslation:[recognizer translationInView:recognizer.view.window] + withVelocity:[recognizer velocityInView:recognizer.view.window] + withNumberOfTouches:recognizer.numberOfTouches]; } @end