mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-03-26 07:04:05 +08:00
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:
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
63
ReactKit/Base/RCTEventDispatcher.h
Normal file
63
ReactKit/Base/RCTEventDispatcher.h
Normal 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
|
||||
153
ReactKit/Base/RCTEventDispatcher.m
Normal file
153
ReactKit/Base/RCTEventDispatcher.m
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -21,16 +21,6 @@
|
||||
}
|
||||
},
|
||||
|
||||
@"RCTRenderingPerf": @{
|
||||
@"moduleID": @(RCTModuleIDRenderingPerf),
|
||||
@"methods": @{
|
||||
@"toggle": @{
|
||||
@"methodID": @(RCTRenderingPerfToggle),
|
||||
@"type": @"local"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@"RCTDeviceEventEmitter": @{
|
||||
@"moduleID": @(RCTModuleIDDeviceEventEmitter),
|
||||
@"methods": @{
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user