2015-02-30 update:

- [ReactNative] Fix indentation | Ben Alpert
- [ReactKit] Bring back ability to jump to syntax error from redbox | Alex Kotliarskyi
- [ReactKit] Update pthread.h import path to be (more?) correct | Ben Alpert
- Simplified event handling | Nick Lockwood
- [ReactNative] more readme - xcode error help | Spencer Ahrens
This commit is contained in:
Christopher Chedeau
2015-02-03 16:02:36 -08:00
parent 0512e23ba9
commit 00f0ebccdf
46 changed files with 596 additions and 1191 deletions

View File

@@ -7,7 +7,7 @@
@protocol RCTNativeModule;
@class RCTUIManager;
@class RCTJavaScriptEventDispatcher;
@class RCTEventDispatcher;
/**
* Functions are the one thing that aren't automatically converted to OBJC
@@ -67,7 +67,7 @@ static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
- (void)enqueueUpdateTimers;
@property (nonatomic, readonly) RCTUIManager *uiManager;
@property (nonatomic, readonly) RCTJavaScriptEventDispatcher *eventDispatcher;
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
// For use in implementing delegates, which may need to queue responses.
- (RCTResponseSenderBlock)createResponseSenderBlock:(NSInteger)callbackID;

View File

@@ -6,7 +6,7 @@
#import "RCTModuleMethod.h"
#import "RCTInvalidating.h"
#import "RCTJavaScriptEventDispatcher.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTModuleIDs.h"
#import "RCTTiming.h"
@@ -94,7 +94,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_javaScriptExecutor = javaScriptExecutor;
_latestJSExecutor = _javaScriptExecutor;
_shadowQueue = shadowQueue;
_eventDispatcher = [[RCTJavaScriptEventDispatcher alloc] initWithBridge:self];
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_moduleInstances = [[NSMutableDictionary alloc] init];

View File

@@ -0,0 +1,63 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
@class RCTBridge;
typedef NS_ENUM(NSInteger, RCTTouchEventType) {
RCTTouchEventTypeStart,
RCTTouchEventTypeMove,
RCTTouchEventTypeEnd,
RCTTouchEventTypeCancel
};
typedef NS_ENUM(NSInteger, RCTTextEventType) {
RCTTextEventTypeFocus,
RCTTextEventTypeBlur,
RCTTextEventTypeChange,
RCTTextEventTypeSubmit,
RCTTextEventTypeEnd
};
typedef NS_ENUM(NSInteger, RCTScrollEventType) {
RCTScrollEventTypeStart,
RCTScrollEventTypeMove,
RCTScrollEventTypeEnd,
RCTScrollEventTypeStartDeceleration,
RCTScrollEventTypeEndDeceleration,
RCTScrollEventTypeEndAnimation,
};
@interface RCTEventDispatcher : NSObject
- (instancetype)initWithBridge:(RCTBridge *)bridge;
/**
* Send an arbitrary event type
*/
- (void)sendRawEventWithType:(NSString *)eventType body:(NSDictionary *)body;
/**
* Send an array of touch events
*/
- (void)sendTouchEventWithType:(RCTTouchEventType)type
touches:(NSArray *)touches
changedIndexes:(NSArray *)changedIndexes;
/**
* Send text events
*/
- (void)sendTextEventWithType:(RCTTextEventType)type
reactTag:(NSNumber *)reactTag
text:(NSString *)text;
/**
* Send scroll events
* (You can send a fake scroll event by passing nil for scrollView)
*/
- (void)sendScrollEventWithType:(RCTScrollEventType)type
reactTag:(NSNumber *)reactTag
scrollView:(UIScrollView *)scrollView
userData:(NSDictionary *)userData;
@end

View File

@@ -0,0 +1,153 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTEventDispatcher.h"
#import "RCTBridge.h"
#import "RCTModuleIDs.h"
#import "UIView+ReactKit.h"
@implementation RCTEventDispatcher
{
RCTBridge *_bridge;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
_bridge = bridge;
}
return self;
}
- (NSArray *)touchEvents
{
static NSArray *events;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
events = @[
@"topTouchStart",
@"topTouchMove",
@"topTouchEnd",
@"topTouchCancel",
];
});
return events;
}
- (void)sendRawEventWithType:(NSString *)eventType body:(NSDictionary *)body
{
static NSSet *touchEvents;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
touchEvents = [NSSet setWithArray:[self touchEvents]];
});
RCTAssert(![touchEvents containsObject:eventType], @"Touch events must be"
"sent via the sendTouchEventWithOrderedTouches: method, not sendRawEventWithType:");
RCTAssert([body[@"target"] isKindOfClass:[NSNumber class]],
@"Event body dictionary must include a 'target' property containing a react tag");
[_bridge enqueueJSCall:RCTModuleIDReactIOSEventEmitter
methodID:RCTEventEmitterReceiveEvent
args:@[body[@"target"], eventType, body]];
}
/**
* Constructs information about touch events to send across the serialized
* boundary. This data should be compliant with W3C `Touch` objects. This data
* alone isn't sufficient to construct W3C `Event` objects. To construct that,
* there must be a simple receiver on the other side of the bridge that
* organizes the touch objects into `Event`s.
*
* We send the data as an array of `Touch`es, the type of action
* (start/end/move/cancel) and the indices that represent "changed" `Touch`es
* from that array.
*/
- (void)sendTouchEventWithType:(RCTTouchEventType)type
touches:(NSArray *)touches
changedIndexes:(NSArray *)changedIndexes
{
RCTAssert(touches.count, @"No touches in touchEventArgsForOrderedTouches");
[_bridge enqueueJSCall:RCTModuleIDReactIOSEventEmitter
methodID:RCTEventEmitterReceiveTouches
args:@[[self touchEvents][type], touches, changedIndexes]];
}
- (void)sendTextEventWithType:(RCTTextEventType)type
reactTag:(NSNumber *)reactTag
text:(NSString *)text
{
static NSArray *events;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
events = @[
@"topFocus",
@"topBlur",
@"topChange",
@"topSubmitEditing",
@"topEndEditing",
];
});
[self sendRawEventWithType:events[type] body:@{
@"text": text,
@"target": reactTag
}];
}
/**
* TODO: throttling
* NOTE: the old system used a per-scrollview throttling
* which would be fairly easy to re-implement if needed,
* but this is non-optimal as it leads to degradation in
* scroll responsiveness. A better solution would be to
* coalesce multiple scroll events into a single batch.
*/
- (void)sendScrollEventWithType:(RCTScrollEventType)type
reactTag:(NSNumber *)reactTag
scrollView:(UIScrollView *)scrollView
userData:(NSDictionary *)userData
{
static NSArray *events;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
events = @[
@"topScrollBeginDrag",
@"topScroll",
@"topScrollEndDrag",
@"topMomentumScrollBegin",
@"topMomentumScrollEnd",
@"topScrollAnimationEnd",
];
});
NSDictionary *body = @{
@"contentOffset": @{
@"x": @(scrollView.contentOffset.x),
@"y": @(scrollView.contentOffset.y)
},
@"contentSize": @{
@"width": @(scrollView.contentSize.width),
@"height": @(scrollView.contentSize.height)
},
@"layoutMeasurement": @{
@"width": @(scrollView.frame.size.width),
@"height": @(scrollView.frame.size.height)
},
@"zoomScale": @(scrollView.zoomScale ?: 1),
@"target": reactTag
};
if (userData) {
NSMutableDictionary *mutableBody = [body mutableCopy];
[mutableBody addEntriesFromDictionary:userData];
body = mutableBody;
}
[self sendRawEventWithType:events[type] body:body];
}
@end

View File

@@ -1,52 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
#import "RCTModuleIDs.h"
/**
* Simple utility to help extract arguments to JS invocations of React event
* emitter (IOS version).
*/
@interface RCTEventExtractor : NSObject
+ (NSArray *)eventArgs:(NSNumber *)tag type:(RCTEventType)type nativeEventObj:(NSDictionary *)nativeEventObj;
/**
* Constructs information about touch events to send across the serialized
* boundary. This data should be compliant with W3C `Touch` objects. This data
* alone isn't sufficient to construct W3C `Event` objects. To construct that,
* there must be a simple receiver on the other side of the bridge that
* organizes the touch objects into `Event`s.
*
* We send the data as an array of `Touch`es, the type of action
* (start/end/move/cancel) and the indices that represent "changed" `Touch`es
* from that array.
*/
+ (NSArray *)touchEventArgsForOrderedTouches:(NSArray *)orderedTouches
orderedStartTags:(NSArray *)orderedStartTags
orderedTouchIDs:(NSArray *)orderedTouchIDs
changedIndices:(NSArray *)changedIndices
type:(RCTEventType)type
view:(UIView *)view;
+ (NSDictionary *)scrollEventObject:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
/**
* Useful when having to simply communicate the fact that *something* scrolled.
* When JavaScript infers gestures based on the event stream, any type of
* scroll that occurs in the native platform will cause ongoing gestures to
* cancel. Scroll/table views already send scroll events appropriately, but
* this method is useful for other views that don't actually scroll, but should
* interrupt JavaScript gestures as scrolls do.
*/
+ (NSDictionary *)fakeScrollEventObjectFor:(NSNumber *)reactTag;
/**
* Finds the React target of a touch. This must be done when the touch starts,
* else `UIKit` gesture recognizers may destroy the touch's target.
*/
+ (NSNumber *)touchStartTarget:(UITouch *)touch inView:(UIView *)view;
@end

View File

@@ -1,200 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTEventExtractor.h"
#import "RCTLog.h"
#import "RCTUIManager.h"
#import "RCTViewNodeProtocol.h"
@implementation RCTEventExtractor
// TODO (#5906496): an array lookup would be better than a switch statement here
/**
* Keep in sync with `IOSEventConstants.js`.
* TODO (#5906496): do this sync programmatically instead of manually
*/
+ (NSString *)topLevelTypeForEventType:(RCTEventType)eventType
{
switch(eventType) {
case RCTEventTap:
return @"topTap";
case RCTEventVisibleCellsChange:
return @"topVisibleCellsChange";
case RCTEventNavigateBack:
return @"topNavigateBack";
case RCTEventNavRightButtonTap:
return @"topNavRightButtonTap";
case RCTEventChange:
return @"topChange";
case RCTEventTextFieldDidFocus:
return @"topFocus";
case RCTEventTextFieldWillBlur:
return @"topBlur";
case RCTEventTextFieldSubmitEditing:
return @"topSubmitEditing";
case RCTEventTextFieldEndEditing:
return @"topEndEditing";
case RCTEventTextInput:
return @"topTextInput";
case RCTEventLongPress:
return @"topLongPress"; // Not yet supported
case RCTEventTouchCancel:
return @"topTouchCancel";
case RCTEventTouchEnd:
return @"topTouchEnd";
case RCTEventTouchMove:
return @"topTouchMove";
case RCTEventTouchStart:
return @"topTouchStart";
case RCTEventScrollBeginDrag:
return @"topScrollBeginDrag";
case RCTEventScroll:
return @"topScroll";
case RCTEventScrollEndDrag:
return @"topScrollEndDrag";
case RCTEventScrollAnimationEnd:
return @"topScrollAnimationEnd";
case RCTEventSelectionChange:
return @"topSelectionChange";
case RCTEventMomentumScrollBegin:
return @"topMomentumScrollBegin";
case RCTEventMomentumScrollEnd:
return @"topMomentumScrollEnd";
case RCTEventPullToRefresh:
return @"topPullToRefresh";
case RCTEventLoadingStart:
return @"topLoadingStart";
case RCTEventLoadingFinish:
return @"topLoadingFinish";
case RCTEventLoadingError:
return @"topLoadingError";
case RCTEventNavigationProgress:
return @"topNavigationProgress";
default :
RCTLogError(@"Unrecognized event type: %tu", eventType);
return @"unknown";
}
}
/**
* TODO (#5906496): Cache created string messages for each event type/tag target (for
* events that have no data) to save allocations.
*/
+ (NSArray *)eventArgs:(NSNumber *)reactTag
type:(RCTEventType)type
nativeEventObj:(NSDictionary *)nativeEventObj
{
NSString *topLevelType = [RCTEventExtractor topLevelTypeForEventType:type];
return @[reactTag ?: @0, topLevelType, nativeEventObj];
}
+ (NSArray *)touchEventArgsForOrderedTouches:(NSArray *)orderedTouches
orderedStartTags:(NSArray *)orderedStartTags
orderedTouchIDs:(NSArray *)orderedTouchIDs
changedIndices:(NSArray *)changedIndices
type:(RCTEventType)type
view:(UIView *)view
{
if (!orderedTouches || !orderedTouches.count) {
RCTLogError(@"No touches in touchEventArgsForOrderedTouches");
return nil;
}
NSMutableArray *touchObjects = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < orderedTouches.count; i++) {
NSDictionary *touchObj =
[RCTEventExtractor touchObj:orderedTouches[i]
withTargetTag:orderedStartTags[i]
withTouchID:orderedTouchIDs[i]
inView:view];
[touchObjects addObject:touchObj];
}
NSString *topLevelType = [RCTEventExtractor topLevelTypeForEventType:type];
return @[topLevelType, touchObjects, changedIndices];
}
+ (NSNumber *)touchStartTarget:(UITouch *)touch inView:(UIView *)view
{
UIView <RCTViewNodeProtocol> *closestReactAncestor = [RCTUIManager closestReactAncestorThatRespondsToTouch:touch];
return [closestReactAncestor reactTag];
}
/**
* Constructs an object that contains all of the important touch data. This
* should contain a superset of a W3C `Touch` object. The `Event` objects that
* reference these touches can only be constructed from a collection of touch
* objects that are recieved across the serialized bridge.
*
* Must accept the `targetReactTag` because targets are reset to `nil` by
* `UIKit` gesture system. We had to pre-recorded at the time of touch start.
*/
+ (NSDictionary *)touchObj:(UITouch *)touch
withTargetTag:(NSNumber *)targetReactTag
withTouchID:(NSNumber *)touchID
inView:(UIView *)view
{
CGPoint location = [touch locationInView:view];
CGPoint locInView = [touch locationInView:touch.view];
double timeStamp = touch.timestamp * 1000.0; // convert to ms
return @{
@"pageX": @(location.x),
@"pageY": @(location.y),
@"locationX": @(locInView.x),
@"locationY": @(locInView.y),
@"target": targetReactTag,
@"identifier": touchID,
@"timeStamp": @(timeStamp),
@"touches": [NSNull null], // We hijack this touchObj to serve both as an event
@"changedTouches": [NSNull null], // and as a Touch object, so making this JIT friendly.
};
}
// TODO (#5906496): shouldn't some of these strings be constants?
+ (NSDictionary *)makeScrollEventObject:(UIScrollView *)uiScrollView reactTag:(NSNumber *)reactTag;
{
return @{
@"contentOffset": @{
@"x": @(uiScrollView.contentOffset.x),
@"y": @(uiScrollView.contentOffset.y)
},
@"contentSize": @{
@"width": @(uiScrollView.contentSize.width),
@"height": @(uiScrollView.contentSize.height)
},
@"layoutMeasurement": @{
@"width": @(uiScrollView.frame.size.width),
@"height": @(uiScrollView.frame.size.height)
},
@"zoomScale": @(uiScrollView.zoomScale),
@"target": reactTag,
};
}
+ (NSDictionary *)scrollEventObject:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
{
return [RCTEventExtractor makeScrollEventObject:scrollView reactTag:reactTag];
}
+ (NSDictionary *)fakeScrollEventObjectFor:(NSNumber *)reactTag
{
return @{
@"contentOffset": @{
@"x": @0,
@"y": @0
},
@"contentSize": @{
@"width": @0,
@"height": @0
},
@"layoutMeasurement": @{
@"width": @0,
@"height": @0
},
@"zoomScale": @1,
@"target": reactTag
};
}
@end

View File

@@ -9,7 +9,7 @@
typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *viewRegistry);
@class RCTJavaScriptEventDispatcher;
@class RCTEventDispatcher;
@class RCTShadowView;
/* ------------------------------------------------------------------- */
@@ -79,7 +79,7 @@ _RCTExportSectionName))) static const RCTExportEntry __rct_export_entry__ = { __
/**
* This method instantiates a native view to be managed by the module.
*/
- (UIView *)viewWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher;
- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
@optional
@@ -172,4 +172,4 @@ RCT_REMAP_VIEW_PROPERTY(name, name)
*/
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry;
@end
@end

View File

@@ -1,14 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <Foundation/Foundation.h>
@class RCTBridge;
@interface RCTJavaScriptEventDispatcher : NSObject
- (instancetype)initWithBridge:(RCTBridge *)bridge;
- (void)sendDeviceEventWithArgs:(NSArray *)args;
- (void)sendEventWithArgs:(NSArray *)args;
- (void)sendTouchesWithArgs:(NSArray *)args;
@end

View File

@@ -1,51 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTJavaScriptEventDispatcher.h"
#import "RCTBridge.h"
#import "RCTModuleIDs.h"
@implementation RCTJavaScriptEventDispatcher
{
RCTBridge *_bridge;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_bridge = bridge;
}
return self;
}
- (void)sendDeviceEventWithArgs:(NSArray *)args
{
if (!args) {
return;
}
[_bridge enqueueJSCall:RCTModuleIDDeviceEventEmitter
methodID:RCTDeviceEventEmitterEmit
args:args];
}
- (void)sendEventWithArgs:(NSArray *)args
{
if (!args) {
return;
}
[_bridge enqueueJSCall:RCTModuleIDReactIOSEventEmitter
methodID:RCTEventEmitterReceiveEvent
args:args];
}
- (void)sendTouchesWithArgs:(NSArray *)args
{
if (!args) {
return;
}
[_bridge enqueueJSCall:RCTModuleIDReactIOSEventEmitter
methodID:RCTEventEmitterReceiveTouches
args:args];
}
@end

View File

@@ -14,7 +14,6 @@ typedef NS_ENUM(NSUInteger, RCTJSModuleIDs) {
RCTModuleIDDimensions,
RCTModuleIDDeviceEventEmitter,
RCTModuleIDNativeAppEventEmitter,
RCTModuleIDRenderingPerf,
};
/**
@@ -25,39 +24,6 @@ typedef NS_ENUM(NSUInteger, RCTEventEmitterRemoteMethodIDs) {
RCTEventEmitterReceiveTouches
};
/**
* `RCTEventEmitter`: Encoding of parameters.
*/
typedef NS_ENUM(NSUInteger, RCTEventType) {
RCTEventTap = 1,
RCTEventVisibleCellsChange,
RCTEventNavigateBack,
RCTEventNavRightButtonTap,
RCTEventChange,
RCTEventTextFieldDidFocus,
RCTEventTextFieldWillBlur,
RCTEventTextFieldSubmitEditing,
RCTEventTextFieldEndEditing,
RCTEventTextInput,
RCTEventLongPress,
RCTEventTouchStart,
RCTEventTouchMove,
RCTEventTouchCancel,
RCTEventTouchEnd,
RCTEventScrollBeginDrag,
RCTEventScroll,
RCTEventScrollEndDrag,
RCTEventSelectionChange,
RCTEventMomentumScrollBegin,
RCTEventMomentumScrollEnd,
RCTEventPullToRefresh,
RCTEventScrollAnimationEnd,
RCTEventLoadingStart,
RCTEventLoadingFinish,
RCTEventLoadingError,
RCTEventNavigationProgress,
};
typedef NS_ENUM(NSUInteger, RCTKeyCode) {
RCTKeyCodeBackspace = 8,
RCTKeyCodeReturn = 13,
@@ -82,18 +48,10 @@ typedef NS_ENUM(NSUInteger, RCTDimensionsMethodIDs) {
RCTDimensionsSet = 0
};
typedef NS_ENUM(NSUInteger, RCTRenderingPerfMethodIDs) {
RCTRenderingPerfToggle = 0,
};
typedef NS_ENUM(NSUInteger, RCTDeviceEventEmitterMethodIDs) {
RCTDeviceEventEmitterEmit = 0
};
typedef NS_ENUM(NSUInteger, RCTNativeAppEventEmitterMethodIDs) {
RCTNativeAppEventEmitterEmit = 0
};
@interface RCTModuleIDs : NSObject
+ (NSDictionary *)config;

View File

@@ -21,16 +21,6 @@
}
},
@"RCTRenderingPerf": @{
@"moduleID": @(RCTModuleIDRenderingPerf),
@"methods": @{
@"toggle": @{
@"methodID": @(RCTRenderingPerfToggle),
@"type": @"local"
},
}
},
@"RCTDeviceEventEmitter": @{
@"moduleID": @(RCTModuleIDDeviceEventEmitter),
@"methods": @{

View File

@@ -1,33 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
// Copyright 2013-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
@class RCTMultiTouchGestureRecognizer;
@protocol RCTMultiTouchGestureRecognizerListener <NSObject>
- (void)handleTouchesStarted:(NSSet *)startedTouches
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
withEvent:(UIEvent *)event;
- (void)handleTouchesMoved:(NSSet *)movedTouches
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
withEvent:(UIEvent *)event;
- (void)handleTouchesEnded:(NSSet *)endedTouches
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
withEvent:(UIEvent *)event;
- (void)handleTouchesCancelled:(NSSet *)cancelledTouches
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
withEvent:(UIEvent *)event;
@end
@interface RCTMultiTouchGestureRecognizer : UIGestureRecognizer
@property (nonatomic, weak) id<RCTMultiTouchGestureRecognizerListener> touchEventDelegate;
@end

View File

@@ -1,103 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
// Copyright 2013-present Facebook. All Rights Reserved.
#import "RCTMultiTouchGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#import "RCTLog.h"
@implementation RCTMultiTouchGestureRecognizer
- (void)touchesBegan:(NSSet *)startedTouches withEvent:(UIEvent *)event {
[super touchesBegan:startedTouches withEvent:event];
if (!self.touchEventDelegate) {
RCTLogError(@"No Touch Delegate for Simple Gesture Recognizer");
return;
}
self.state = UIGestureRecognizerStateBegan;
[self.touchEventDelegate handleTouchesStarted:startedTouches
forMultiGestureRecognizer:self
withEvent:event];
}
- (void)touchesMoved:(NSSet *)movedTouches withEvent:(UIEvent *)event {
[super touchesMoved:movedTouches withEvent:event];
if (self.state == UIGestureRecognizerStateFailed) {
return;
}
[self.touchEventDelegate handleTouchesMoved:movedTouches
forMultiGestureRecognizer:self
withEvent:event];
}
- (void)touchesEnded:(NSSet *)endedTouches withEvent:(UIEvent *)event {
[super touchesEnded:endedTouches withEvent:event];
[self.touchEventDelegate handleTouchesEnded:endedTouches
forMultiGestureRecognizer:self
withEvent:event];
// These may be a different set than the total set of touches.
NSSet *touches = [event touchesForGestureRecognizer:self];
BOOL hasEnded = [self _allTouchesAreCanceledOrEnded:touches];
if (hasEnded) {
self.state = UIGestureRecognizerStateEnded;
} else if ([self _anyTouchesChanged:touches]) {
self.state = UIGestureRecognizerStateChanged;
}
}
- (void)touchesCancelled:(NSSet *)cancelledTouches withEvent:(UIEvent *)event {
[super touchesCancelled:cancelledTouches withEvent:event];
[self.touchEventDelegate handleTouchesCancelled:cancelledTouches
forMultiGestureRecognizer:self
withEvent:event];
// These may be a different set than the total set of touches.
NSSet *touches = [event touchesForGestureRecognizer:self];
BOOL hasCanceled = [self _allTouchesAreCanceledOrEnded:touches];
if (hasCanceled) {
self.state = UIGestureRecognizerStateFailed;
} else if ([self _anyTouchesChanged:touches]) {
self.state = UIGestureRecognizerStateChanged;
}
}
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
{
return NO;
}
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer
{
return NO;
}
#pragma mark - Private
- (BOOL)_allTouchesAreCanceledOrEnded:(NSSet *)touches
{
for (UITouch *touch in touches) {
if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
return NO;
} else if (touch.phase == UITouchPhaseStationary) {
return NO;
}
}
return YES;
}
- (BOOL)_anyTouchesChanged:(NSSet *)touches
{
for (UITouch *touch in touches) {
if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
return YES;
}
}
return NO;
}
@end

View File

@@ -4,12 +4,9 @@
#import "RCTBridge.h"
#import "RCTContextExecutor.h"
#import "RCTEventDispatcher.h"
#import "RCTJavaScriptAppEngine.h"
#import "RCTJavaScriptEventDispatcher.h"
#import "RCTModuleIDs.h"
#import "RCTRedBox.h"
#import "RCTShadowView.h"
#import "RCTSparseArray.h"
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"

View File

@@ -1,24 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
@class RCTJavaScriptEventDispatcher;
/**
* Handles throttling of scroll events that are dispatched to JavaScript.
*/
@interface RCTScrollDispatcher : NSObject
@property (nonatomic, readwrite, assign) NSInteger throttleScrollCallbackMS;
- (instancetype)initWithEventDispatcher:(RCTJavaScriptEventDispatcher *)dispatcher;
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
@end

View File

@@ -1,131 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTScrollDispatcher.h"
#import "RCTBridge.h"
#import "RCTEventExtractor.h"
#import "RCTJavaScriptEventDispatcher.h"
#import "UIView+ReactKit.h"
@implementation RCTScrollDispatcher
{
RCTJavaScriptEventDispatcher *_eventDispatcher;
NSTimeInterval _lastScrollDispatchTime;
BOOL _allowNextScrollNoMatterWhat;
NSMutableDictionary *_cachedChildFrames;
CGPoint _lastContentOffset;
}
- (instancetype)initWithEventDispatcher:(RCTJavaScriptEventDispatcher *)dispatcher
{
if (self = [super init]) {
_eventDispatcher = dispatcher;
_throttleScrollCallbackMS = 0;
_lastScrollDispatchTime = CACurrentMediaTime();
_cachedChildFrames = [NSMutableDictionary new];
}
return self;
}
- (NSArray *)_getUpdatedChildFrames:(UIScrollView *)scrollView forUpdateKey:(NSString *)updateKey
{
NSArray *children = [scrollView.subviews[0] reactSubviews];
NSMutableArray *updatedChildFrames = [NSMutableArray new];
NSMutableArray *cachedFrames = _cachedChildFrames[updateKey];
if (!cachedFrames) {
cachedFrames = [[NSMutableArray alloc] initWithCapacity:children.count];
_cachedChildFrames[updateKey] = cachedFrames;
}
for (int ii = 0; ii < children.count; ii++) {
CGRect newFrame = [children[ii] frame];
if (cachedFrames.count <= ii || !CGRectEqualToRect(newFrame, [cachedFrames[ii] CGRectValue])) {
[updatedChildFrames addObject:
@{
@"index": @(ii),
@"x": @(newFrame.origin.x),
@"y": @(newFrame.origin.y),
@"width": @(newFrame.size.width),
@"height": @(newFrame.size.height),
}];
NSValue *frameObj = [NSValue valueWithCGRect:newFrame];
if (cachedFrames.count <= ii) {
[cachedFrames addObject:frameObj];
} else {
cachedFrames[ii] = frameObj;
}
}
}
return updatedChildFrames;
}
- (void)_dispatchScroll:(UIScrollView *)scrollView forUpdateKey:(NSString *)updateKey reactTag:(NSNumber *)reactTag
{
NSTimeInterval now = CACurrentMediaTime();
NSTimeInterval dt = now - _lastScrollDispatchTime;
NSMutableDictionary *mutableNativeObj = [[RCTEventExtractor scrollEventObject:scrollView reactTag:reactTag] mutableCopy];
if (updateKey) {
NSArray *updatedChildFrames = [self _getUpdatedChildFrames:scrollView forUpdateKey:updateKey];
if (updatedChildFrames.count > 0) {
mutableNativeObj[@"updatedChildFrames"] = updatedChildFrames;
}
}
mutableNativeObj[@"velocity"] = @{
@"x": @((scrollView.contentOffset.x - _lastContentOffset.x) / dt),
@"y": @((scrollView.contentOffset.y - _lastContentOffset.y) / dt),
};
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:reactTag
type:RCTEventScroll
nativeEventObj:mutableNativeObj]];
_lastScrollDispatchTime = now;
_lastContentOffset = scrollView.contentOffset;
_allowNextScrollNoMatterWhat = NO;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
{
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:reactTag
type:RCTEventScrollAnimationEnd
nativeEventObj:[RCTEventExtractor scrollEventObject:scrollView reactTag:reactTag]]];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
{
NSTimeInterval now = CACurrentMediaTime();
NSTimeInterval throttleScrollCallbackSeconds = _throttleScrollCallbackMS / 1000.0f;
if (_allowNextScrollNoMatterWhat ||
(_throttleScrollCallbackMS != 0 && throttleScrollCallbackSeconds < (now - _lastScrollDispatchTime))) {
[self _dispatchScroll:scrollView forUpdateKey:@"didScroll" reactTag:reactTag];
}
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
{
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:reactTag
type:RCTEventMomentumScrollBegin
nativeEventObj:[RCTEventExtractor scrollEventObject:scrollView reactTag:reactTag]]];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
{
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:reactTag
type:RCTEventMomentumScrollEnd
nativeEventObj:[RCTEventExtractor scrollEventObject:scrollView reactTag:reactTag]]];
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
{
_allowNextScrollNoMatterWhat = YES;
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:reactTag
type:RCTEventScrollBeginDrag
nativeEventObj:[RCTEventExtractor scrollEventObject:scrollView reactTag:reactTag]]];
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
{
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:reactTag
type:RCTEventScrollEndDrag
nativeEventObj:[RCTEventExtractor scrollEventObject:scrollView reactTag:reactTag]]];
}
@end

View File

@@ -14,19 +14,17 @@
- (instancetype)initWithCapacity:(NSUInteger)capacity
{
if (self = [super init]) {
if ((self = [super init])) {
_storage = [NSMutableDictionary dictionaryWithCapacity:capacity];
}
return self;
}
- (instancetype)initWithSparseArray:(RCTSparseArray *)sparseArray
{
if (self = [super init]) {
if ((self = [super init])) {
_storage = [sparseArray->_storage copy];
}
return self;
}

View File

@@ -2,31 +2,11 @@
#import <UIKit/UIKit.h>
@class RCTJavaScriptEventDispatcher;
@class RCTEventDispatcher;
@interface RCTTouchHandler : NSObject
@interface RCTTouchHandler : UIGestureRecognizer
- (instancetype)initWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
rootView:(UIView *)rootView;
@property (nonatomic, readwrite, strong) RCTJavaScriptEventDispatcher *eventDispatcher;
/**
* Maintaining the set of active touches by the time they started touching.
*/
@property (nonatomic, readonly, strong) NSMutableArray *orderedTouches;
/**
* Array managed in parallel to `orderedTouches` tracking original `reactTag`
* for each touch. This must be kept track of because `UIKit` destroys the
* touch targets if touches are canceled and we have no other way to recover
* this information.
*/
@property (nonatomic, readonly, strong) NSMutableArray *orderedTouchStartTags;
/**
* IDs that uniquely represent a touch among all of the active touches.
*/
@property (nonatomic, readonly, strong) NSMutableArray *orderedTouchIDs;
@end

View File

@@ -2,23 +2,29 @@
#import "RCTTouchHandler.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#import "RCTAssert.h"
#import "RCTEventExtractor.h"
#import "RCTJavaScriptEventDispatcher.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTMultiTouchGestureRecognizer.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "UIView+ReactKit.h"
@interface RCTTouchHandler () <RCTMultiTouchGestureRecognizerListener, UIGestureRecognizerDelegate>
@end
@implementation RCTTouchHandler
{
UIView *_rootView;
NSMutableArray *_gestureRecognizers;
__weak UIView *_rootView;
RCTEventDispatcher *_eventDispatcher;
/**
* Arrays managed in parallel tracking native touch object along with the
* native view that was touched, and the react touch data dictionary.
* This must be kept track of because `UIKit` destroys the touch targets
* if touches are canceled and we have no other way to recover this information.
*/
NSMutableOrderedSet *_nativeTouches;
NSMutableArray *_reactTouches;
NSMutableArray *_touchViews;
}
- (instancetype)init
@@ -26,113 +32,60 @@
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (instancetype)initWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher
- (instancetype)initWithTarget:(id)target action:(SEL)action
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
rootView:(UIView *)rootView
{
if (self = [super init]) {
if ((self = [super initWithTarget:nil action:NULL])) {
RCTAssert(eventDispatcher != nil, @"Expect an event dispatcher");
RCTAssert(rootView != nil, @"Expect a root view");
_eventDispatcher = eventDispatcher;
_rootView = rootView;
_gestureRecognizers = [NSMutableArray new];
_orderedTouches = [[NSMutableArray alloc] init];
_orderedTouchStartTags = [[NSMutableArray alloc] init];
_orderedTouchIDs = [[NSMutableArray alloc] init];
[self _loadGestureRecognizers];
_nativeTouches = [[NSMutableOrderedSet alloc] init];
_reactTouches = [[NSMutableArray alloc] init];
_touchViews = [[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.
self.cancelsTouchesInView = NO;
[_rootView addGestureRecognizer:self];
}
return self;
}
- (void)dealloc
{
[self removeGestureRecognizers];
}
#pragma mark - Gesture Recognizers
- (void)_loadGestureRecognizers
{
[self _addRecognizerForEvent:RCTEventTap];
[self _addRecognizerForEvent:RCTEventLongPress];
[self _addRecognizerForSimpleTouchEvents];
}
- (void)_addRecognizerForSimpleTouchEvents
{
RCTMultiTouchGestureRecognizer *multiTouchRecognizer =
[[RCTMultiTouchGestureRecognizer alloc] initWithTarget:self action:@selector(handleMultiTouchGesture:)];
multiTouchRecognizer.touchEventDelegate = self;
[self _addRecognizer:multiTouchRecognizer];
}
- (void)_addRecognizerForEvent:(RCTEventType)event
{
UIGestureRecognizer *recognizer = nil;
switch (event) {
case RCTEventTap:
recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
((UITapGestureRecognizer *)recognizer).numberOfTapsRequired = 1;
break;
case RCTEventVisibleCellsChange:
case RCTEventNavigateBack:
case RCTEventNavRightButtonTap:
case RCTEventChange:
case RCTEventTextFieldDidFocus:
case RCTEventTextFieldWillBlur:
case RCTEventTextFieldSubmitEditing:
case RCTEventTextFieldEndEditing:
case RCTEventScroll:
break;
case RCTEventLongPress:
recognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
break;
default:
RCTLogError(@"Unrecognized event type for gesture: %zd", event);
}
[self _addRecognizer:recognizer];
}
- (void)_addRecognizer:(UIGestureRecognizer *)recognizer
{
// `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.
recognizer.cancelsTouchesInView = NO;
recognizer.delegate = self;
[_gestureRecognizers addObject:recognizer];
[_rootView addGestureRecognizer:recognizer];
}
- (void)removeGestureRecognizers
{
for (UIGestureRecognizer *recognizer in _gestureRecognizers) {
[recognizer setDelegate:nil];
[recognizer removeTarget:nil action:NULL];
[_rootView removeGestureRecognizer:recognizer];
}
}
#pragma mark - Bookkeeping for touch indices
- (void)_recordNewTouches:(NSSet *)touches
{
for (UITouch *touch in touches) {
NSUInteger currentIndex = [_orderedTouches indexOfObject:touch];
if (currentIndex != NSNotFound) {
RCTLogError(@"Touch is already recorded. This is a critical bug.");
[_orderedTouches removeObjectAtIndex:currentIndex];
[_orderedTouchStartTags removeObjectAtIndex:currentIndex];
[_orderedTouchIDs removeObjectAtIndex:currentIndex];
RCTAssert(![_nativeTouches containsObject:touch],
@"Touch is already recorded. This is a critical bug.");
// Find closest React-managed touchable view
UIView *targetView = touch.view;
while (targetView) {
if (targetView.reactTag && targetView.userInteractionEnabled) { // TODO: implement respondsToTouch: mechanism
break;
}
targetView = targetView.superview;
}
NSNumber *touchStartTag = [RCTEventExtractor touchStartTarget:touch inView:_rootView];
RCTAssert(targetView.reactTag && targetView.userInteractionEnabled,
@"No react view found for touch - something went wrong.");
// Get new, unique touch id
const NSUInteger RCTMaxTouches = 11; // This is the maximum supported by iDevices
NSInteger touchID = ([_orderedTouchIDs.lastObject integerValue] + 1) % RCTMaxTouches;
for (NSNumber *n in _orderedTouchIDs) {
NSInteger usedID = [n integerValue];
NSInteger touchID = ([_reactTouches.lastObject[@"target"] integerValue] + 1) % RCTMaxTouches;
for (NSDictionary *reactTouch in _reactTouches) {
NSInteger usedID = [reactTouch[@"target"] integerValue];
if (usedID == touchID) {
// ID has already been used, try next value
touchID ++;
@@ -141,156 +94,117 @@
break;
}
}
[_orderedTouches addObject:touch];
[_orderedTouchStartTags addObject:touchStartTag];
[_orderedTouchIDs addObject:@(touchID)];
// Create touch
NSMutableDictionary *reactTouch = [[NSMutableDictionary alloc] initWithCapacity:9];
reactTouch[@"target"] = targetView.reactTag;
reactTouch[@"identifier"] = @(touchID);
reactTouch[@"touches"] = [NSNull null]; // We hijack this touchObj to serve both as an event
reactTouch[@"changedTouches"] = [NSNull null]; // and as a Touch object, so making this JIT friendly.
// Add to arrays
[_touchViews addObject:targetView];
[_nativeTouches addObject:touch];
[_reactTouches addObject:reactTouch];
}
}
- (void)_recordRemovedTouches:(NSSet *)touches
{
for (UITouch *touch in touches) {
NSUInteger currentIndex = [_orderedTouches indexOfObject:touch];
if (currentIndex == NSNotFound) {
RCTLogError(@"Touch is already removed. This is a critical bug.");
} else {
[_orderedTouches removeObjectAtIndex:currentIndex];
[_orderedTouchStartTags removeObjectAtIndex:currentIndex];
[_orderedTouchIDs removeObjectAtIndex:currentIndex];
}
NSUInteger index = [_nativeTouches indexOfObject:touch];
RCTAssert(index != NSNotFound, @"Touch is already removed. This is a critical bug.");
[_touchViews removeObjectAtIndex:index];
[_nativeTouches removeObjectAtIndex:index];
[_reactTouches removeObjectAtIndex:index];
}
}
- (void)_updateReactTouchAtIndex:(NSInteger)touchIndex
{
UITouch *nativeTouch = _nativeTouches[touchIndex];
CGPoint windowLocation = [nativeTouch locationInView:nativeTouch.window];
CGPoint rootViewLocation = [nativeTouch.window convertPoint:windowLocation toView:_rootView];
UIView *touchView = _touchViews[touchIndex];
CGPoint touchViewLocation = [nativeTouch.window convertPoint:windowLocation toView:touchView];
NSMutableDictionary *reactTouch = _reactTouches[touchIndex];
reactTouch[@"pageX"] = @(rootViewLocation.x);
reactTouch[@"pageY"] = @(rootViewLocation.y);
reactTouch[@"locationX"] = @(touchViewLocation.x);
reactTouch[@"locationY"] = @(touchViewLocation.y);
reactTouch[@"timestamp"] = @(nativeTouch.timestamp * 1000); // in ms, for JS
}
- (void)_updateAndDispatchTouches:(NSSet *)touches eventType:(RCTTouchEventType)eventType
{
// Update touches
NSMutableArray *changedIndices = [[NSMutableArray alloc] init];
for (UITouch *touch in touches) {
NSInteger index = [_nativeTouches indexOfObject:touch];
RCTAssert(index != NSNotFound, @"Touch not found. This is a critical bug.");
[self _updateReactTouchAtIndex:index];
[changedIndices addObject:@(index)];
}
// 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 *reactTouches = [[NSMutableArray alloc] initWithCapacity:_reactTouches.count];
for (NSDictionary *touch in _reactTouches) {
[reactTouches addObject:[touch copy]];
}
// Dispatch touch event
[_eventDispatcher sendTouchEventWithType:eventType
touches:reactTouches
changedIndexes:changedIndices];
}
#pragma mark - Gesture Recognizer Delegate Callbacks
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (NSArray *)_indicesOfTouches:(NSSet *)touchSet inArray:(NSArray *)array
{
NSMutableArray *result = [[NSMutableArray alloc] init];
for (UITouch *touch in touchSet) {
[result addObject:@([array indexOfObject:touch])];
}
return result;
}
- (void)handleTouchesStarted:(NSSet *)startedTouches
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
withEvent:(UIEvent *)event
- (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:startedTouches];
NSArray *indicesOfStarts = [self _indicesOfTouches:startedTouches inArray:_orderedTouches];
NSArray *args =
[RCTEventExtractor touchEventArgsForOrderedTouches:_orderedTouches
orderedStartTags:_orderedTouchStartTags
orderedTouchIDs:_orderedTouchIDs
changedIndices:indicesOfStarts
type:RCTEventTouchStart
view:_rootView];
[_eventDispatcher sendTouchesWithArgs:args];
[self _recordNewTouches:touches];
[self _updateAndDispatchTouches:touches eventType:RCTTouchEventTypeStart];
}
- (void)handleTouchesMoved:(NSSet *)movedTouches
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
withEvent:(UIEvent *)event
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSArray *indicesOfMoves = [self _indicesOfTouches:movedTouches inArray:_orderedTouches];
NSArray *args =
[RCTEventExtractor touchEventArgsForOrderedTouches:_orderedTouches
orderedStartTags:_orderedTouchStartTags
orderedTouchIDs:_orderedTouchIDs
changedIndices:indicesOfMoves
type:RCTEventTouchMove
view:_rootView];
[_eventDispatcher sendTouchesWithArgs:args];
}
- (void)handleTouchesEnded:(NSSet *)endedTouches
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
withEvent:(UIEvent *)event
{
NSArray *indicesOfEnds = [self _indicesOfTouches:endedTouches inArray:_orderedTouches];
NSArray *args =
[RCTEventExtractor touchEventArgsForOrderedTouches:_orderedTouches
orderedStartTags:_orderedTouchStartTags
orderedTouchIDs:_orderedTouchIDs
changedIndices:indicesOfEnds
type:RCTEventTouchEnd
view:_rootView];
[_eventDispatcher sendTouchesWithArgs:args];
[self _recordRemovedTouches:endedTouches];
}
- (void)handleTouchesCancelled:(NSSet *)cancelledTouches
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
withEvent:(UIEvent *)event
{
NSArray *indicesOfCancels = [self _indicesOfTouches:cancelledTouches inArray:_orderedTouches];
NSArray *args =
[RCTEventExtractor touchEventArgsForOrderedTouches:_orderedTouches
orderedStartTags:_orderedTouchStartTags
orderedTouchIDs:_orderedTouchIDs
changedIndices:indicesOfCancels
type:RCTEventTouchCancel
view:_rootView];
[_eventDispatcher sendTouchesWithArgs:args];
[self _recordRemovedTouches:cancelledTouches];
}
/**
* Needed simply to be provided to the `RCTMultiTouchGestureRecognizer`. If not,
* other gestures are cancelled.
*/
- (void)handleMultiTouchGesture:(UIGestureRecognizer *)sender
{
}
- (NSDictionary *)_nativeEventForGesture:(UIGestureRecognizer *)sender
target:(UIView *)target
reactTargetView:(UIView *)reactTargetView
{
return @{
@"state": @(sender.state),
@"target": reactTargetView.reactTag,
};
}
- (void)handleTap:(UIGestureRecognizer *)sender
{
// This calculation may not be accurate when views overlap.
UIView *touchedView = sender.view;
CGPoint location = [sender locationInView:touchedView];
UIView *target = [touchedView hitTest:location withEvent:nil];
// Views outside the RCT system can be present (e.g., UITableViewCellContentView)
// they have no registry. we can safely ignore events happening on them.
if (sender.state == UIGestureRecognizerStateEnded) {
UIView *reactTargetView = [RCTUIManager closestReactAncestor:target];
if (reactTargetView) {
NSMutableDictionary *nativeEvent =[[self _nativeEventForGesture:sender target:target reactTargetView:reactTargetView] mutableCopy];
nativeEvent[@"pageX"] = @(location.x);
nativeEvent[@"pageY"] = @(location.y);
CGPoint locInView = [sender.view convertPoint:location toView:target];
nativeEvent[@"locationX"] = @(locInView.x);
nativeEvent[@"locationY"] = @(locInView.y);
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:[reactTargetView reactTag]
type:RCTEventTap
nativeEventObj:nativeEvent]];
}
[super touchesMoved:touches withEvent:event];
if (self.state == UIGestureRecognizerStateFailed) {
return;
}
[self _updateAndDispatchTouches:touches eventType:RCTTouchEventTypeMove];
}
- (void)handleLongPress:(UIGestureRecognizer *)sender
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self _updateAndDispatchTouches:touches eventType:RCTTouchEventTypeEnd];
[self _recordRemovedTouches:touches];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self _updateAndDispatchTouches:touches eventType:RCTTouchEventTypeCancel];
[self _recordRemovedTouches:touches];
}
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
{
return NO;
}
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer
{
return NO;
}
@end