From 2d66e10ec45b9fbbf005d7d110f57c22cf972338 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Fri, 7 Aug 2015 10:46:42 -0700 Subject: [PATCH] [ReactNative] Delay sending touches to JS until dependent (native) gesture recognizers fail Summary: Previously, JS touches were being interpreted simultaneously with native gesture recognizers. --- React/Base/RCTTouchHandler.m | 66 ++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m index c7c19ed9b..227e1a424 100644 --- a/React/Base/RCTTouchHandler.m +++ b/React/Base/RCTTouchHandler.m @@ -23,6 +23,7 @@ @implementation RCTTouchHandler { __weak RCTBridge *_bridge; + BOOL _dispatchedInitialTouches; /** * Arrays managed in parallel tracking native touch object along with the @@ -42,9 +43,10 @@ { RCTAssertParam(bridge); - if ((self = [super initWithTarget:nil action:NULL])) { + if ((self = [super initWithTarget:self action:@selector(handleGestureUpdate:)])) { _bridge = bridge; + _dispatchedInitialTouches = NO; _nativeTouches = [[NSMutableOrderedSet alloc] init]; _reactTouches = [[NSMutableArray alloc] init]; _touchViews = [[NSMutableArray alloc] init]; @@ -216,6 +218,17 @@ static BOOL RCTAnyTouchesChanged(NSSet *touches) return NO; } +- (void)handleGestureUpdate:(UIGestureRecognizer *)gesture { + // If the gesture just recognized, send all the touches over to JS as if they just began + if (self.state == UIGestureRecognizerStateBegan) { + [self _updateAndDispatchTouches:[_nativeTouches set] eventName:@"topTouchStart" originatingTime:0]; + + // We store this flag separately from `state` because after a gesture is recognized, its `state` changes immediately but its action (that is, this method) isn't fired until dependent gesture recognizers have failed. We only want to send move/end/cancel touch updates if we've sent the touchStart events. + _dispatchedInitialTouches = YES; + } + // For the other states, we could dispatch the updates here but since we specifically send info about which touches changed, it's simpler to dispatch the updates from the raw touch methods below. +} + - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesBegan:touches withEvent:event]; @@ -223,21 +236,20 @@ static BOOL RCTAnyTouchesChanged(NSSet *touches) // "start" has to record new touches before extracting the event. // "end"/"cancel" needs to remove the touch *after* extracting the event. [self _recordNewTouches:touches]; - [self _updateAndDispatchTouches:touches eventName:@"topTouchStart" originatingTime:event.timestamp]; - - self.state = UIGestureRecognizerStateBegan; + if (_dispatchedInitialTouches) { + [self _updateAndDispatchTouches:touches eventName:@"topTouchStart" originatingTime:event.timestamp]; + self.state = UIGestureRecognizerStateChanged; + } else { + self.state = UIGestureRecognizerStateBegan; + } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesMoved:touches withEvent:event]; - if (self.state == UIGestureRecognizerStateFailed) { - return; - } - [self _updateAndDispatchTouches:touches eventName:@"topTouchMove" originatingTime:event.timestamp]; - - if (self.state == UIGestureRecognizerStateBegan) { + if (_dispatchedInitialTouches) { + [self _updateAndDispatchTouches:touches eventName:@"topTouchMove" originatingTime:event.timestamp]; self.state = UIGestureRecognizerStateChanged; } } @@ -245,27 +257,33 @@ static BOOL RCTAnyTouchesChanged(NSSet *touches) - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesEnded:touches withEvent:event]; - [self _updateAndDispatchTouches:touches eventName:@"topTouchEnd" originatingTime:event.timestamp]; - [self _recordRemovedTouches:touches]; - if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) { - self.state = UIGestureRecognizerStateEnded; - } else if (RCTAnyTouchesChanged(event.allTouches)) { - self.state = UIGestureRecognizerStateChanged; + if (_dispatchedInitialTouches) { + [self _updateAndDispatchTouches:touches eventName:@"topTouchEnd" originatingTime:event.timestamp]; + + if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) { + self.state = UIGestureRecognizerStateEnded; + } else if (RCTAnyTouchesChanged(event.allTouches)) { + self.state = UIGestureRecognizerStateChanged; + } } + [self _recordRemovedTouches:touches]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesCancelled:touches withEvent:event]; - [self _updateAndDispatchTouches:touches eventName:@"topTouchCancel" originatingTime:event.timestamp]; - [self _recordRemovedTouches:touches]; - if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) { - self.state = UIGestureRecognizerStateCancelled; - } else if (RCTAnyTouchesChanged(event.allTouches)) { - self.state = UIGestureRecognizerStateChanged; + if (_dispatchedInitialTouches) { + [self _updateAndDispatchTouches:touches eventName:@"topTouchCancel" originatingTime:event.timestamp]; + + if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) { + self.state = UIGestureRecognizerStateCancelled; + } else if (RCTAnyTouchesChanged(event.allTouches)) { + self.state = UIGestureRecognizerStateChanged; + } } + [self _recordRemovedTouches:touches]; } - (BOOL)canPreventGestureRecognizer:(__unused UIGestureRecognizer *)preventedGestureRecognizer @@ -278,4 +296,8 @@ static BOOL RCTAnyTouchesChanged(NSSet *touches) return NO; } +- (void)reset { + _dispatchedInitialTouches = NO; +} + @end