From acafa7e9217465179fb22c90efeb764df56fbcce Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 5 May 2015 05:58:07 -0700 Subject: [PATCH] [ReactNative] Properly transition RCTTouchHandler state Summary: When touches end or cancel, update self.state in RCTTouchHandler to let iOS know that we are in an ended/canceled state. This way we won't eat other touches because it still thinks we're in a began/changed state. @public Test Plan: Scrolling in the back swipe area no longer busts gesture recognition in Wilde. --- React/Base/RCTTouchHandler.m | 52 +++++++++++++++++++++++++++++++++--- React/Layout/Layout.c | 13 --------- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m index 7af26da74..2af5c428c 100644 --- a/React/Base/RCTTouchHandler.m +++ b/React/Base/RCTTouchHandler.m @@ -55,8 +55,9 @@ _pendingTouches = [[NSMutableArray alloc] init]; _bridgeInteractionTiming = [[NSMutableArray alloc] init]; - // `cancelsTouchesInView` is needed in order to be used as a top level event delegated recognizer. Otherwise, lower - // level components not build using RCT, will fail to recognize gestures. + // `cancelsTouchesInView` is needed in order to be used as a top level + // event delegated recognizer. Otherwise, lower-level components not built + // using RCT, will fail to recognize gestures. self.cancelsTouchesInView = NO; } return self; @@ -165,7 +166,9 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches); * (start/end/move/cancel) and the indices that represent "changed" `Touch`es * from that array. */ -- (void)_updateAndDispatchTouches:(NSSet *)touches eventName:(NSString *)eventName originatingTime:(CFTimeInterval)originatingTime +- (void)_updateAndDispatchTouches:(NSSet *)touches + eventName:(NSString *)eventName + originatingTime:(CFTimeInterval)originatingTime { // Update touches NSMutableArray *changedIndexes = [[NSMutableArray alloc] init]; @@ -196,15 +199,39 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches); #pragma mark - Gesture Recognizer Delegate Callbacks +static BOOL RCTAllTouchesAreCancelldOrEnded(NSSet *touches) +{ + for (UITouch *touch in touches) { + if (touch.phase == UITouchPhaseBegan || + touch.phase == UITouchPhaseMoved || + touch.phase == UITouchPhaseStationary) { + return NO; + } + } + return YES; +} + +static BOOL RCTAnyTouchesChanged(NSSet *touches) +{ + for (UITouch *touch in touches) { + if (touch.phase == UITouchPhaseBegan || + touch.phase == UITouchPhaseMoved) { + return YES; + } + } + return NO; +} + - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesBegan:touches withEvent:event]; - self.state = UIGestureRecognizerStateBegan; // "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; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event @@ -213,7 +240,12 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches); if (self.state == UIGestureRecognizerStateFailed) { return; } + [self _updateAndDispatchTouches:touches eventName:@"topTouchMove" originatingTime:event.timestamp]; + + if (self.state == UIGestureRecognizerStateBegan) { + self.state = UIGestureRecognizerStateChanged; + } } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event @@ -221,6 +253,12 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches); [super touchesEnded:touches withEvent:event]; [self _updateAndDispatchTouches:touches eventName:@"topTouchEnd" originatingTime:event.timestamp]; [self _recordRemovedTouches:touches]; + + if (RCTAllTouchesAreCancelldOrEnded(event.allTouches)) { + self.state = UIGestureRecognizerStateEnded; + } else if (RCTAnyTouchesChanged(event.allTouches)) { + self.state = UIGestureRecognizerStateChanged; + } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event @@ -228,6 +266,12 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches); [super touchesCancelled:touches withEvent:event]; [self _updateAndDispatchTouches:touches eventName:@"topTouchCancel" originatingTime:event.timestamp]; [self _recordRemovedTouches:touches]; + + if (RCTAllTouchesAreCancelldOrEnded(event.allTouches)) { + self.state = UIGestureRecognizerStateCancelled; + } else if (RCTAnyTouchesChanged(event.allTouches)) { + self.state = UIGestureRecognizerStateChanged; + } } - (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer diff --git a/React/Layout/Layout.c b/React/Layout/Layout.c index 50d69b522..9ed711cd0 100644 --- a/React/Layout/Layout.c +++ b/React/Layout/Layout.c @@ -702,19 +702,6 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { } } - float containerMainAxis = node->layout.dimensions[dim[mainAxis]]; - // If the user didn't specify a width or height, and it has not been set - // by the container, then we set it via the children. - if (isUndefined(containerMainAxis)) { - containerMainAxis = fmaxf( - // We're missing the last padding at this point to get the final - // dimension - boundAxis(node, mainAxis, mainDim + getPaddingAndBorder(node, trailing[mainAxis])), - // We can never assign a width smaller than the padding and borders - getPaddingAndBorderAxis(node, mainAxis) - ); - } - float containerCrossAxis = node->layout.dimensions[dim[crossAxis]]; if (isUndefined(node->layout.dimensions[dim[crossAxis]])) { containerCrossAxis = fmaxf(