Permalink
Browse files

New way to handle simultaneously active gesture recognizers in RCTTou…

…chHandler

Reviewed By: majak

Differential Revision: D4385209

fbshipit-source-id: 22746dd93e378a4d9e5feca66bc84c357afb6f36
  • Loading branch information...
shergin authored and facebook-github-bot committed Jan 12, 2017
1 parent dbe555b commit c68a70862168cd3df751c5110a0062493397dc9d
Showing with 67 additions and 64 deletions.
  1. +1 −0 React/Base/RCTTouchEvent.m
  2. +66 −64 React/Base/RCTTouchHandler.m
@@ -8,6 +8,7 @@
*/
#import "RCTTouchEvent.h"
#import "RCTAssert.h"
@implementation RCTTouchEvent
@@ -20,6 +20,9 @@
#import "RCTUtils.h"
#import "UIView+React.h"
@interface RCTTouchHandler () <UIGestureRecognizerDelegate>
@end
// TODO: this class behaves a lot like a module, and could be implemented as a
// module if we were to assume that modules and RootViews had a 1:1 relationship
@implementation RCTTouchHandler
@@ -36,20 +39,16 @@ @implementation RCTTouchHandler
NSMutableArray<NSMutableDictionary *> *_reactTouches;
NSMutableArray<UIView *> *_touchViews;
BOOL _dispatchedInitialTouches;
BOOL _recordingInteractionTiming;
CFTimeInterval _mostRecentEnqueueJS;
uint16_t _coalescingKey;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
RCTAssertParam(bridge);
if ((self = [super initWithTarget:self action:@selector(handleGestureUpdate:)])) {
if ((self = [super initWithTarget:nil action:NULL])) {
_eventDispatcher = [bridge moduleForClass:[RCTEventDispatcher class]];
_dispatchedInitialTouches = NO;
_nativeTouches = [NSMutableOrderedSet new];
_reactTouches = [NSMutableArray new];
_touchViews = [NSMutableArray new];
@@ -58,7 +57,10 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
// event delegated recognizer. Otherwise, lower-level components not built
// using RCT, will fail to recognize gestures.
self.cancelsTouchesInView = NO;
self.delegate = self;
}
return self;
}
@@ -79,13 +81,6 @@ - (void)detachFromView:(UIView *)view
[view removeGestureRecognizer:self];
}
typedef NS_ENUM(NSInteger, RCTTouchEventType) {
RCTTouchEventTypeStart,
RCTTouchEventTypeMove,
RCTTouchEventTypeEnd,
RCTTouchEventTypeCancel
};
#pragma mark - Bookkeeping for touch indices
- (void)_recordNewTouches:(NSSet<UITouch *> *)touches
@@ -187,7 +182,6 @@ - (void)_updateReactTouchAtIndex:(NSInteger)touchIndex
*/
- (void)_updateAndDispatchTouches:(NSSet<UITouch *> *)touches
eventName:(NSString *)eventName
originatingTime:(__unused CFTimeInterval)originatingTime
{
// Update touches
NSMutableArray<NSNumber *> *changedIndexes = [NSMutableArray new];
@@ -208,16 +202,31 @@ - (void)_updateAndDispatchTouches:(NSSet<UITouch *> *)touches
// Deep copy the touches because they will be accessed from another thread
// TODO: would it be safer to do this in the bridge or executor, rather than trusting caller?
NSMutableArray<NSDictionary *> *reactTouches =
[[NSMutableArray alloc] initWithCapacity:_reactTouches.count];
[[NSMutableArray alloc] initWithCapacity:_reactTouches.count];
for (NSDictionary *touch in _reactTouches) {
[reactTouches addObject:[touch copy]];
}
BOOL canBeCoalesced = [eventName isEqualToString:@"touchMove"];
// We increment `_coalescingKey` twice here just for sure that
// this `_coalescingKey` will not be reused by ahother (preceding or following) event
// (yes, even if coalescing only happens (and makes sense) on events of the same type).
if (!canBeCoalesced) {
_coalescingKey++;
}
RCTTouchEvent *event = [[RCTTouchEvent alloc] initWithEventName:eventName
reactTag:self.view.reactTag
reactTouches:reactTouches
changedIndexes:changedIndexes
coalescingKey:_coalescingKey];
if (!canBeCoalesced) {
_coalescingKey++;
}
[_eventDispatcher sendEvent:event];
}
@@ -246,81 +255,60 @@ static BOOL RCTAnyTouchesChanged(NSSet<UITouch *> *touches)
return NO;
}
- (void)handleGestureUpdate:(__unused UIGestureRecognizer *)gesture
{
// If gesture just recognized, send all touches 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 (this
// method) isn't fired until dependent gesture recognizers have failed. We
// only want to send move/end/cancel touches if we've sent the touchStart.
_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.
}
#pragma mark - `UIResponder`-ish touch-delivery methods
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
_coalescingKey++;
// "start" has to record new touches before extracting the event.
// "start" has to record new touches *before* extracting the event.
// "end"/"cancel" needs to remove the touch *after* extracting the event.
[self _recordNewTouches:touches];
if (_dispatchedInitialTouches) {
[self _updateAndDispatchTouches:touches eventName:@"touchStart" originatingTime:event.timestamp];
self.state = UIGestureRecognizerStateChanged;
} else {
[self _updateAndDispatchTouches:touches eventName:@"touchStart"];
if (self.state == UIGestureRecognizerStatePossible) {
self.state = UIGestureRecognizerStateBegan;
} else if (self.state == UIGestureRecognizerStateBegan) {
self.state = UIGestureRecognizerStateChanged;
}
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
if (_dispatchedInitialTouches) {
[self _updateAndDispatchTouches:touches eventName:@"touchMove" originatingTime:event.timestamp];
self.state = UIGestureRecognizerStateChanged;
}
[self _updateAndDispatchTouches:touches eventName:@"touchMove"];
self.state = UIGestureRecognizerStateChanged;
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
_coalescingKey++;
if (_dispatchedInitialTouches) {
[self _updateAndDispatchTouches:touches eventName:@"touchEnd" originatingTime:event.timestamp];
[self _updateAndDispatchTouches:touches eventName:@"touchEnd"];
if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) {
self.state = UIGestureRecognizerStateEnded;
} else if (RCTAnyTouchesChanged(event.allTouches)) {
self.state = UIGestureRecognizerStateChanged;
}
if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) {
self.state = UIGestureRecognizerStateEnded;
} else if (RCTAnyTouchesChanged(event.allTouches)) {
self.state = UIGestureRecognizerStateChanged;
}
[self _recordRemovedTouches:touches];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
_coalescingKey++;
if (_dispatchedInitialTouches) {
[self _updateAndDispatchTouches:touches eventName:@"touchCancel" originatingTime:event.timestamp];
[self _updateAndDispatchTouches:touches eventName:@"touchCancel"];
if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) {
self.state = UIGestureRecognizerStateCancelled;
} else if (RCTAnyTouchesChanged(event.allTouches)) {
self.state = UIGestureRecognizerStateChanged;
}
if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) {
self.state = UIGestureRecognizerStateCancelled;
} else if (RCTAnyTouchesChanged(event.allTouches)) {
self.state = UIGestureRecognizerStateChanged;
}
[self _recordRemovedTouches:touches];
}
@@ -329,24 +317,38 @@ - (BOOL)canPreventGestureRecognizer:(__unused UIGestureRecognizer *)preventedGes
return NO;
}
- (BOOL)canBePreventedByGestureRecognizer:(__unused UIGestureRecognizer *)preventingGestureRecognizer
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer
{
return NO;
// We fail in favour of other external gesture recognizers.
// iOS will ask `delegate`'s opinion about this gesture recognizer little bit later.
return ![preventingGestureRecognizer.view isDescendantOfView:self.view];
}
- (void)reset
{
_dispatchedInitialTouches = NO;
if (_nativeTouches.count != 0) {
[self _updateAndDispatchTouches:_nativeTouches.set eventName:@"touchCancel"];
[_nativeTouches removeAllObjects];
[_reactTouches removeAllObjects];
[_touchViews removeAllObjects];
[_nativeTouches removeAllObjects];
[_reactTouches removeAllObjects];
[_touchViews removeAllObjects];
}
}
#pragma mark - Other
- (void)cancel
{
self.enabled = NO;
self.enabled = YES;
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
// Same condition for `failure of` as for `be prevented by`.
return [self canBePreventedByGestureRecognizer:otherGestureRecognizer];
}
@end

0 comments on commit c68a708

Please sign in to comment.