diff --git a/Examples/UIExplorer/NavigatorIOSExample.js b/Examples/UIExplorer/NavigatorIOSExample.js index 13a9e4726..dc04c27c8 100644 --- a/Examples/UIExplorer/NavigatorIOSExample.js +++ b/Examples/UIExplorer/NavigatorIOSExample.js @@ -53,6 +53,8 @@ var NavigatorIOSExample = React.createClass({ + + {this._renderRow(recurseTitle, () => { this.props.navigator.push({ @@ -182,7 +184,9 @@ var styles = StyleSheet.create({ }, group: { backgroundColor: 'white', - paddingVertical: 10, + }, + groupSpace: { + height: 15, }, line: { backgroundColor: '#bbbbbb', @@ -192,7 +196,7 @@ var styles = StyleSheet.create({ backgroundColor: 'white', justifyContent: 'center', paddingHorizontal: 15, - paddingVertical: 8, + paddingVertical: 15, }, separator: { height: 1 / PixelRatio.get(), diff --git a/README.md b/README.md index 53ca1ad02..f0c5d7c79 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,10 @@ Get up and running with our Movies sample app: 1. Once you have the repo cloned and met all the requirements above, start the packager that will transform your JS code on-the-fly: -``` -npm install -npm start -``` + ``` + npm install + npm start + ``` 2. Open the `Examples/Movies/Movies.xcodeproj` project in Xcode. 3. Make sure the target is set to `Movies` and that you have an iOS simulator selected to run the app. @@ -61,22 +61,19 @@ Feel free to browse the Movies sample files and customize various properties to get familiar with the codebase and React Native. Also check out the UI Component Explorer for more sample code: -`Examples/UIExplorer/UIExplorer.xcodeproj`. **Make sure to stop any running apps -before running a new one or Xcode might hang.** +`Examples/UIExplorer/UIExplorer.xcodeproj`. **Make sure to close the Movies +project first - Xcode will break if you have two projects open that reference +the same library.** ## Troubleshooting -If Xcode hangs, force kill it with the activity monitor. This sometimes happens -if you try to run another app without manually stopping the previous one first. +Xcode will break if you have two examples open at the same time. Jest testing does not yet work on node versions after 0.10.x. You can verify the packager is working by loading the [bundle](http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle) in your browser and inspecting the contents. -You might see weird npm install errors - we recommend uninstalling and -reinstalling node and other software with brew instead. - Please report any other issues you encounter so we can fix them ASAP. ## Basics diff --git a/ReactKit/Base/RCTBridge.h b/ReactKit/Base/RCTBridge.h index a78d58c3e..81c68f415 100644 --- a/ReactKit/Base/RCTBridge.h +++ b/ReactKit/Base/RCTBridge.h @@ -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; diff --git a/ReactKit/Base/RCTBridge.m b/ReactKit/Base/RCTBridge.m index 1369657d8..4105960d8 100644 --- a/ReactKit/Base/RCTBridge.m +++ b/ReactKit/Base/RCTBridge.m @@ -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 _latestJSExecutor; _javaScriptExecutor = javaScriptExecutor; _latestJSExecutor = _javaScriptExecutor; _shadowQueue = shadowQueue; - _eventDispatcher = [[RCTJavaScriptEventDispatcher alloc] initWithBridge:self]; + _eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; _moduleInstances = [[NSMutableDictionary alloc] init]; diff --git a/ReactKit/Base/RCTEventDispatcher.h b/ReactKit/Base/RCTEventDispatcher.h new file mode 100644 index 000000000..3f9e8b2a3 --- /dev/null +++ b/ReactKit/Base/RCTEventDispatcher.h @@ -0,0 +1,63 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +@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 diff --git a/ReactKit/Base/RCTEventDispatcher.m b/ReactKit/Base/RCTEventDispatcher.m new file mode 100644 index 000000000..a7484fab0 --- /dev/null +++ b/ReactKit/Base/RCTEventDispatcher.m @@ -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 diff --git a/ReactKit/Base/RCTEventExtractor.h b/ReactKit/Base/RCTEventExtractor.h deleted file mode 100644 index 4238b72f7..000000000 --- a/ReactKit/Base/RCTEventExtractor.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -#import - -#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 - diff --git a/ReactKit/Base/RCTEventExtractor.m b/ReactKit/Base/RCTEventExtractor.m deleted file mode 100644 index d6e2dd861..000000000 --- a/ReactKit/Base/RCTEventExtractor.m +++ /dev/null @@ -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 *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 - diff --git a/ReactKit/Base/RCTExport.h b/ReactKit/Base/RCTExport.h index 56a538009..11ca17693 100644 --- a/ReactKit/Base/RCTExport.h +++ b/ReactKit/Base/RCTExport.h @@ -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 \ No newline at end of file +@end diff --git a/ReactKit/Base/RCTJavaScriptEventDispatcher.h b/ReactKit/Base/RCTJavaScriptEventDispatcher.h deleted file mode 100644 index 7fc0d3e49..000000000 --- a/ReactKit/Base/RCTJavaScriptEventDispatcher.h +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -#import - -@class RCTBridge; - -@interface RCTJavaScriptEventDispatcher : NSObject - -- (instancetype)initWithBridge:(RCTBridge *)bridge; -- (void)sendDeviceEventWithArgs:(NSArray *)args; -- (void)sendEventWithArgs:(NSArray *)args; -- (void)sendTouchesWithArgs:(NSArray *)args; - -@end diff --git a/ReactKit/Base/RCTJavaScriptEventDispatcher.m b/ReactKit/Base/RCTJavaScriptEventDispatcher.m deleted file mode 100644 index 070aeb2e6..000000000 --- a/ReactKit/Base/RCTJavaScriptEventDispatcher.m +++ /dev/null @@ -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 diff --git a/ReactKit/Base/RCTModuleIDs.h b/ReactKit/Base/RCTModuleIDs.h index d9b7ce6cf..f03f69002 100644 --- a/ReactKit/Base/RCTModuleIDs.h +++ b/ReactKit/Base/RCTModuleIDs.h @@ -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; diff --git a/ReactKit/Base/RCTModuleIDs.m b/ReactKit/Base/RCTModuleIDs.m index b93866c1c..e83707906 100644 --- a/ReactKit/Base/RCTModuleIDs.m +++ b/ReactKit/Base/RCTModuleIDs.m @@ -21,16 +21,6 @@ } }, - @"RCTRenderingPerf": @{ - @"moduleID": @(RCTModuleIDRenderingPerf), - @"methods": @{ - @"toggle": @{ - @"methodID": @(RCTRenderingPerfToggle), - @"type": @"local" - }, - } - }, - @"RCTDeviceEventEmitter": @{ @"moduleID": @(RCTModuleIDDeviceEventEmitter), @"methods": @{ diff --git a/ReactKit/Base/RCTMultiTouchGestureRecognizer.h b/ReactKit/Base/RCTMultiTouchGestureRecognizer.h deleted file mode 100644 index a4b0f444e..000000000 --- a/ReactKit/Base/RCTMultiTouchGestureRecognizer.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -// Copyright 2013-present Facebook. All Rights Reserved. - -#import - -@class RCTMultiTouchGestureRecognizer; - -@protocol RCTMultiTouchGestureRecognizerListener - -- (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 touchEventDelegate; - -@end diff --git a/ReactKit/Base/RCTMultiTouchGestureRecognizer.m b/ReactKit/Base/RCTMultiTouchGestureRecognizer.m deleted file mode 100644 index 93c06d9ab..000000000 --- a/ReactKit/Base/RCTMultiTouchGestureRecognizer.m +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -// Copyright 2013-present Facebook. All Rights Reserved. - -#import "RCTMultiTouchGestureRecognizer.h" - -#import - -#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 diff --git a/ReactKit/Base/RCTRootView.m b/ReactKit/Base/RCTRootView.m index bfe2466f1..6f46b841d 100644 --- a/ReactKit/Base/RCTRootView.m +++ b/ReactKit/Base/RCTRootView.m @@ -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" diff --git a/ReactKit/Base/RCTScrollDispatcher.h b/ReactKit/Base/RCTScrollDispatcher.h deleted file mode 100644 index 9ab812e0b..000000000 --- a/ReactKit/Base/RCTScrollDispatcher.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -#import - -@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 - diff --git a/ReactKit/Base/RCTScrollDispatcher.m b/ReactKit/Base/RCTScrollDispatcher.m deleted file mode 100644 index ba68d0421..000000000 --- a/ReactKit/Base/RCTScrollDispatcher.m +++ /dev/null @@ -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 diff --git a/ReactKit/Base/RCTSparseArray.m b/ReactKit/Base/RCTSparseArray.m index 520053c3b..ca88d2db6 100644 --- a/ReactKit/Base/RCTSparseArray.m +++ b/ReactKit/Base/RCTSparseArray.m @@ -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; } diff --git a/ReactKit/Base/RCTTouchHandler.h b/ReactKit/Base/RCTTouchHandler.h index 175b04d13..8019ade46 100644 --- a/ReactKit/Base/RCTTouchHandler.h +++ b/ReactKit/Base/RCTTouchHandler.h @@ -2,31 +2,11 @@ #import -@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 diff --git a/ReactKit/Base/RCTTouchHandler.m b/ReactKit/Base/RCTTouchHandler.m index d4a5fe1f6..09071bc3e 100644 --- a/ReactKit/Base/RCTTouchHandler.m +++ b/ReactKit/Base/RCTTouchHandler.m @@ -2,23 +2,29 @@ #import "RCTTouchHandler.h" +#import + #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 () - -@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 diff --git a/ReactKit/Executors/RCTWebViewExecutor.m b/ReactKit/Executors/RCTWebViewExecutor.m index b34a3aa1d..307661799 100644 --- a/ReactKit/Executors/RCTWebViewExecutor.m +++ b/ReactKit/Executors/RCTWebViewExecutor.m @@ -33,7 +33,7 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...) if (!webView) { @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Can't init with a nil webview" userInfo:nil]; } - if (self = [super init]) { + if ((self = [super init])) { _objectsToInject = [[NSMutableDictionary alloc] init]; _webView = webView; _webView.delegate = self; diff --git a/ReactKit/Modules/RCTTiming.m b/ReactKit/Modules/RCTTiming.m index 9dd199d68..e1a3f8cca 100644 --- a/ReactKit/Modules/RCTTiming.m +++ b/ReactKit/Modules/RCTTiming.m @@ -25,7 +25,7 @@ targetTime:(NSTimeInterval)targetTime repeats:(BOOL)repeats { - if (self = [super init]) { + if ((self = [super init])) { _active = YES; _interval = interval; _repeats = repeats; @@ -62,7 +62,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge { - if (self = [super init]) { + if ((self = [super init])) { _bridge = bridge; _timers = [[RCTSparseArray alloc] init]; } diff --git a/ReactKit/Modules/RCTUIManager.h b/ReactKit/Modules/RCTUIManager.h index 096ac19b2..32cc0f3c3 100644 --- a/ReactKit/Modules/RCTUIManager.h +++ b/ReactKit/Modules/RCTUIManager.h @@ -5,20 +5,19 @@ #import "RCTExport.h" #import "RCTInvalidating.h" +@class RCTAnimationRegistry; +@class RCTRootView; +@class RCTShadowView; + @protocol RCTScrollableProtocol; @protocol RCTViewNodeProtocol; -@class RCTRootView; -@class RCTJavaScriptEventDispatcher; -@class RCTShadowView; -@class RCTAnimationRegistry; - @interface RCTUIManager : NSObject - (instancetype)initWithShadowQueue:(dispatch_queue_t)shadowQueue viewManagers:(NSDictionary *)viewManagers; -@property (nonatomic, strong) RCTJavaScriptEventDispatcher *eventDispatcher; +@property (nonatomic, strong) RCTEventDispatcher *eventDispatcher; @property (nonatomic, strong) RCTSparseArray *shadowViewRegistry; @property (nonatomic, strong) RCTSparseArray *viewRegistry; @property (nonatomic, strong) RCTAnimationRegistry *animationRegistry; diff --git a/ReactKit/Modules/RCTUIManager.m b/ReactKit/Modules/RCTUIManager.m index 7d21d6af3..129572f4a 100644 --- a/ReactKit/Modules/RCTUIManager.m +++ b/ReactKit/Modules/RCTUIManager.m @@ -4,7 +4,7 @@ #import #import -#import +#import #import "Layout.h" #import "RCTAssert.h" @@ -26,7 +26,8 @@ typedef void (^react_view_node_block_t)(id); -static void RCTTraverseViewNodes(id view, react_view_node_block_t block) { +static void RCTTraverseViewNodes(id view, react_view_node_block_t block) +{ if (view.reactTag) block(view); for (id subview in view.reactSubviews) { RCTTraverseViewNodes(subview, block); diff --git a/ReactKit/ReactKit.xcodeproj/project.pbxproj b/ReactKit/ReactKit.xcodeproj/project.pbxproj index 286dc278a..5d2bbc692 100644 --- a/ReactKit/ReactKit.xcodeproj/project.pbxproj +++ b/ReactKit/ReactKit.xcodeproj/project.pbxproj @@ -17,7 +17,6 @@ 137029491A698FF000575408 /* RCTNetworkImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137029401A698FF000575408 /* RCTNetworkImageViewManager.m */; }; 137029501A6990A100575408 /* RCTNetworkImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1370294F1A6990A100575408 /* RCTNetworkImageView.m */; }; 137029531A69923600575408 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 137029521A69923600575408 /* RCTImageDownloader.m */; }; - 137029561A6C17DC00575408 /* RCTScrollDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 137029551A6C17DC00575408 /* RCTScrollDispatcher.m */; }; 13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */; }; 13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FE81A69327A00A75B9A /* RCTAlertManager.m */; }; 13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */; }; @@ -49,13 +48,11 @@ 83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA501A601E3B00E9B192 /* RCTUtils.m */; }; 83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA591A601E9000E9B192 /* RCTRedBox.m */; }; 83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */; }; - 83CBBA691A601EF300E9B192 /* RCTJavaScriptEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA661A601EF300E9B192 /* RCTJavaScriptEventDispatcher.m */; }; + 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */; }; 83CBBA871A60202500E9B192 /* RCTJavaScriptAppEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA861A60202500E9B192 /* RCTJavaScriptAppEngine.m */; }; 83CBBA8B1A60204600E9B192 /* RCTModuleIDs.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA8A1A60204600E9B192 /* RCTModuleIDs.m */; }; 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */; }; - 83CBBAA11A60215500E9B192 /* RCTEventExtractor.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBAA01A60215500E9B192 /* RCTEventExtractor.m */; }; 83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBACB1A6023D300E9B192 /* RCTConvert.m */; }; - 83CBBAD81A60250F00E9B192 /* RCTMultiTouchGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBAD71A60250F00E9B192 /* RCTMultiTouchGestureRecognizer.m */; }; 83EEC2EE1A604AB200C39218 /* RCTModuleMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 83EEC2ED1A604AB200C39218 /* RCTModuleMethod.m */; }; /* End PBXBuildFile section */ @@ -87,8 +84,6 @@ 1370294F1A6990A100575408 /* RCTNetworkImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkImageView.m; sourceTree = ""; }; 137029511A69923600575408 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = ""; }; 137029521A69923600575408 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = ""; }; - 137029541A6C17DC00575408 /* RCTScrollDispatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTScrollDispatcher.h; sourceTree = ""; }; - 137029551A6C17DC00575408 /* RCTScrollDispatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTScrollDispatcher.m; sourceTree = ""; }; 137029571A6C197000575408 /* RCTRawTextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRawTextManager.h; sourceTree = ""; }; 137029581A6C197000575408 /* RCTRawTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRawTextManager.m; sourceTree = ""; }; 13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTKeyCommands.h; sourceTree = ""; }; @@ -163,20 +158,16 @@ 83CBBA611A601EB200E9B192 /* RCTAutoInsetsProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTAutoInsetsProtocol.h; sourceTree = ""; }; 83CBBA621A601EB800E9B192 /* RCTViewNodeProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTViewNodeProtocol.h; sourceTree = ""; }; 83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptExecutor.h; sourceTree = ""; }; - 83CBBA651A601EF300E9B192 /* RCTJavaScriptEventDispatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptEventDispatcher.h; sourceTree = ""; }; - 83CBBA661A601EF300E9B192 /* RCTJavaScriptEventDispatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptEventDispatcher.m; sourceTree = ""; }; + 83CBBA651A601EF300E9B192 /* RCTEventDispatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTEventDispatcher.h; sourceTree = ""; }; + 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTEventDispatcher.m; sourceTree = ""; }; 83CBBA851A60202500E9B192 /* RCTJavaScriptAppEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptAppEngine.h; sourceTree = ""; }; 83CBBA861A60202500E9B192 /* RCTJavaScriptAppEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptAppEngine.m; sourceTree = ""; }; 83CBBA891A60204600E9B192 /* RCTModuleIDs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleIDs.h; sourceTree = ""; }; 83CBBA8A1A60204600E9B192 /* RCTModuleIDs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleIDs.m; sourceTree = ""; }; 83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTouchHandler.h; sourceTree = ""; }; 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTouchHandler.m; sourceTree = ""; }; - 83CBBA9F1A60215500E9B192 /* RCTEventExtractor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTEventExtractor.h; sourceTree = ""; }; - 83CBBAA01A60215500E9B192 /* RCTEventExtractor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTEventExtractor.m; sourceTree = ""; }; 83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTConvert.h; sourceTree = ""; }; 83CBBACB1A6023D300E9B192 /* RCTConvert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert.m; sourceTree = ""; }; - 83CBBAD61A60250F00E9B192 /* RCTMultiTouchGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultiTouchGestureRecognizer.h; sourceTree = ""; }; - 83CBBAD71A60250F00E9B192 /* RCTMultiTouchGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultiTouchGestureRecognizer.m; sourceTree = ""; }; 83EEC2EC1A604AB200C39218 /* RCTModuleMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleMethod.h; sourceTree = ""; }; 83EEC2ED1A604AB200C39218 /* RCTModuleMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethod.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -324,8 +315,6 @@ 83EEC2ED1A604AB200C39218 /* RCTModuleMethod.m */, 83CBBACA1A6023D300E9B192 /* RCTConvert.h */, 83CBBACB1A6023D300E9B192 /* RCTConvert.m */, - 83CBBA9F1A60215500E9B192 /* RCTEventExtractor.h */, - 83CBBAA01A60215500E9B192 /* RCTEventExtractor.m */, 830213F31A654E0800B993E6 /* RCTExport.h */, 830213F41A65574D00B993E6 /* RCTExport.m */, 830A229C1A66C68A008503DA /* RCTRootView.h */, @@ -333,8 +322,8 @@ 83CBBA4C1A601E3B00E9B192 /* RCTInvalidating.h */, 83CBBA851A60202500E9B192 /* RCTJavaScriptAppEngine.h */, 83CBBA861A60202500E9B192 /* RCTJavaScriptAppEngine.m */, - 83CBBA651A601EF300E9B192 /* RCTJavaScriptEventDispatcher.h */, - 83CBBA661A601EF300E9B192 /* RCTJavaScriptEventDispatcher.m */, + 83CBBA651A601EF300E9B192 /* RCTEventDispatcher.h */, + 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */, 83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */, 83BEE46C1A6D19BC00B5863B /* RCTSparseArray.h */, 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */, @@ -342,8 +331,6 @@ 83CBBA4E1A601E3B00E9B192 /* RCTLog.m */, 83CBBA891A60204600E9B192 /* RCTModuleIDs.h */, 83CBBA8A1A60204600E9B192 /* RCTModuleIDs.m */, - 83CBBAD61A60250F00E9B192 /* RCTMultiTouchGestureRecognizer.h */, - 83CBBAD71A60250F00E9B192 /* RCTMultiTouchGestureRecognizer.m */, 83CBBA581A601E9000E9B192 /* RCTRedBox.h */, 83CBBA591A601E9000E9B192 /* RCTRedBox.m */, 83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */, @@ -354,8 +341,6 @@ 137029511A69923600575408 /* RCTImageDownloader.h */, 137029521A69923600575408 /* RCTImageDownloader.m */, 13B07FCD1A683B5F00A75B9A /* RCTScrollableProtocol.h */, - 137029541A6C17DC00575408 /* RCTScrollDispatcher.h */, - 137029551A6C17DC00575408 /* RCTScrollDispatcher.m */, ); path = Base; sourceTree = ""; @@ -461,12 +446,10 @@ 137029501A6990A100575408 /* RCTNetworkImageView.m in Sources */, 13E067571A70F44B002CDEE1 /* RCTView.m in Sources */, 134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */, - 83CBBAD81A60250F00E9B192 /* RCTMultiTouchGestureRecognizer.m in Sources */, 137029491A698FF000575408 /* RCTNetworkImageViewManager.m in Sources */, 13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */, 13E067581A70F44B002CDEE1 /* RCTViewManager.m in Sources */, 83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */, - 137029561A6C17DC00575408 /* RCTScrollDispatcher.m in Sources */, 83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */, 13B080081A6947C200A75B9A /* RCTShadowText.m in Sources */, 13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */, @@ -476,8 +459,7 @@ 83EEC2EE1A604AB200C39218 /* RCTModuleMethod.m in Sources */, 13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */, 137029331A69659C00575408 /* RCTExport.m in Sources */, - 83CBBA691A601EF300E9B192 /* RCTJavaScriptEventDispatcher.m in Sources */, - 83CBBAA11A60215500E9B192 /* RCTEventExtractor.m in Sources */, + 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */, 134FCB371A6D4ED700051CC8 /* RCTRawTextManager.m in Sources */, 13B0800B1A6947C200A75B9A /* RCTTextManager.m in Sources */, 13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */, diff --git a/ReactKit/Views/RCTNavItemManager.m b/ReactKit/Views/RCTNavItemManager.m index 8e8546d00..e509af80f 100644 --- a/ReactKit/Views/RCTNavItemManager.m +++ b/ReactKit/Views/RCTNavItemManager.m @@ -7,7 +7,7 @@ @implementation RCTNavItemManager -- (UIView *)viewWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { return [[RCTNavItem alloc] initWithFrame:CGRectZero]; } diff --git a/ReactKit/Views/RCTNavigator.h b/ReactKit/Views/RCTNavigator.h index a288e5e8a..e88b45ce9 100644 --- a/ReactKit/Views/RCTNavigator.h +++ b/ReactKit/Views/RCTNavigator.h @@ -2,7 +2,7 @@ #import -@class RCTJavaScriptEventDispatcher; +@class RCTEventDispatcher; @interface RCTNavigator : UIView @@ -10,7 +10,7 @@ @property (nonatomic, assign) NSInteger requestedTopOfStack; - (instancetype)initWithFrame:(CGRect)frame - eventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher; + eventDispatcher:(RCTEventDispatcher *)eventDispatcher; /** * Schedules a JavaScript navigation and prevents `UIKit` from navigating until diff --git a/ReactKit/Views/RCTNavigator.m b/ReactKit/Views/RCTNavigator.m index 21d58e154..97d545948 100644 --- a/ReactKit/Views/RCTNavigator.m +++ b/ReactKit/Views/RCTNavigator.m @@ -4,8 +4,7 @@ #import "RCTAssert.h" #import "RCTConvert.h" -#import "RCTEventExtractor.h" -#import "RCTJavaScriptEventDispatcher.h" +#import "RCTEventDispatcher.h" #import "RCTLog.h" #import "RCTNavItem.h" #import "RCTUtils.h" @@ -138,7 +137,7 @@ NSInteger kNeverProgressed = -10000; */ - (instancetype)initWithScrollCallback:(dispatch_block_t)callback { - if (self = [super init]) { + if ((self = [super init])) { _scrollCallback = callback; } return self; @@ -193,7 +192,7 @@ NSInteger kNeverProgressed = -10000; @interface RCTNavigator() { - RCTJavaScriptEventDispatcher *_eventDispatcher; + RCTEventDispatcher *_eventDispatcher; NSInteger _numberOfViewControllerMovesToIgnore; } @@ -267,7 +266,7 @@ NSInteger kNeverProgressed = -10000; @implementation RCTNavigator - (id)initWithFrame:(CGRect)frame - eventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher + eventDispatcher:(RCTEventDispatcher *)eventDispatcher { self = [super initWithFrame:frame]; if (self) { @@ -311,16 +310,12 @@ NSInteger kNeverProgressed = -10000; if (nextProgress == _mostRecentProgress) { return; } - NSDictionary *nativeEventObj = @{ - @"fromIndex": @(_currentlyTransitioningFrom), - @"toIndex": @(_currentlyTransitioningTo), - @"progress": @(nextProgress), - @"target": self.reactTag ?: @0, - }; _mostRecentProgress = nextProgress; - [_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:self.reactTag - type:RCTEventNavigationProgress - nativeEventObj:nativeEventObj]]; + [_eventDispatcher sendRawEventWithType:@"topNavigationProgress" + body:@{@"fromIndex": @(_currentlyTransitioningFrom), + @"toIndex": @(_currentlyTransitioningTo), + @"progress": @(nextProgress), + @"target": self.reactTag}]; } } @@ -443,21 +438,17 @@ NSInteger kNeverProgressed = -10000; - (void)handleTopOfStackChanged { - NSDictionary *nativeEventObj = @{ - @"target":self.reactTag ?: @0, - @"stackLength":@(_navigationController.viewControllers.count) - }; - NSArray *eventArgs = [RCTEventExtractor eventArgs:self.reactTag - type:RCTEventNavigateBack - nativeEventObj:nativeEventObj]; - [_eventDispatcher sendEventWithArgs:eventArgs]; + [_eventDispatcher sendRawEventWithType:@"topNavigateBack" + body:@{@"target":self.reactTag, + @"stackLength":@(_navigationController.viewControllers.count)}]; } - (void)dispatchFakeScrollEvent { - [_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:[self reactTag] - type:RCTEventScroll - nativeEventObj:[RCTEventExtractor fakeScrollEventObjectFor:[self reactTag]]]]; + [_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove + reactTag:self.reactTag + scrollView:nil + userData:nil]; } /** diff --git a/ReactKit/Views/RCTNavigatorManager.m b/ReactKit/Views/RCTNavigatorManager.m index 6bb08f836..317ff4865 100644 --- a/ReactKit/Views/RCTNavigatorManager.m +++ b/ReactKit/Views/RCTNavigatorManager.m @@ -8,7 +8,7 @@ @implementation RCTNavigatorManager -- (UIView *)viewWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { return [[RCTNavigator alloc] initWithFrame:CGRectZero eventDispatcher:eventDispatcher]; } diff --git a/ReactKit/Views/RCTNetworkImageViewManager.m b/ReactKit/Views/RCTNetworkImageViewManager.m index 0a5d3098d..b1235d82b 100644 --- a/ReactKit/Views/RCTNetworkImageViewManager.m +++ b/ReactKit/Views/RCTNetworkImageViewManager.m @@ -11,7 +11,7 @@ @implementation RCTNetworkImageViewManager -- (UIView *)viewWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { RCTNetworkImageView *view = [[RCTNetworkImageView alloc] initWithFrame:CGRectZero imageDownloader:[RCTImageDownloader sharedInstance]]; view.contentMode = UIViewContentModeScaleAspectFill; diff --git a/ReactKit/Views/RCTRawTextManager.m b/ReactKit/Views/RCTRawTextManager.m index e838b8f56..1d007e88f 100644 --- a/ReactKit/Views/RCTRawTextManager.m +++ b/ReactKit/Views/RCTRawTextManager.m @@ -6,7 +6,7 @@ @implementation RCTRawTextManager -- (UIView *)viewWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { return [[UIView alloc] init]; } diff --git a/ReactKit/Views/RCTScrollView.h b/ReactKit/Views/RCTScrollView.h index a7d596202..9de2892e6 100644 --- a/ReactKit/Views/RCTScrollView.h +++ b/ReactKit/Views/RCTScrollView.h @@ -8,7 +8,7 @@ @protocol UIScrollViewDelegate; -@class RCTJavaScriptEventDispatcher; +@class RCTEventDispatcher; @interface RCTScrollView : RCTView @@ -32,6 +32,6 @@ @property (nonatomic, assign) BOOL centerContent; @property (nonatomic, copy) NSArray *stickyHeaderIndices; -- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher; +- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTEventDispatcher *)eventDispatcher; @end diff --git a/ReactKit/Views/RCTScrollView.m b/ReactKit/Views/RCTScrollView.m index 5d48ecc9c..0af80319b 100644 --- a/ReactKit/Views/RCTScrollView.m +++ b/ReactKit/Views/RCTScrollView.m @@ -5,10 +5,8 @@ #import #import "RCTConvert.h" -#import "RCTEventExtractor.h" -#import "RCTJavaScriptEventDispatcher.h" +#import "RCTEventDispatcher.h" #import "RCTLog.h" -#import "RCTScrollDispatcher.h" #import "RCTUIManager.h" #import "UIView+ReactKit.h" @@ -249,24 +247,20 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; @end -@interface RCTScrollView () +@implementation RCTScrollView { - RCTJavaScriptEventDispatcher *_eventDispatcher; + RCTEventDispatcher *_eventDispatcher; BOOL _contentSizeManuallySet; - RCTScrollDispatcher *_scrollDispatcher; RCTCustomScrollView *_scrollView; UIView *_contentView; + NSTimeInterval _lastScrollDispatchTime; + NSMutableArray *_cachedChildFrames; + BOOL _allowNextScrollNoMatterWhat; } -@property (nonatomic, readwrite, assign) BOOL didThrottleMomentumScrollEvent; - -@end - -@implementation RCTScrollView - @synthesize nativeMainScrollDelegate = _nativeMainScrollDelegate; -- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTEventDispatcher *)eventDispatcher { if ((self = [super initWithFrame:frame])) { @@ -274,9 +268,12 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; _scrollView = [[RCTCustomScrollView alloc] initWithFrame:CGRectZero]; _scrollView.delegate = self; _scrollView.delaysContentTouches = NO; - _scrollDispatcher = [[RCTScrollDispatcher alloc] initWithEventDispatcher:eventDispatcher]; _automaticallyAdjustContentInsets = YES; _contentInset = UIEdgeInsetsZero; + + _throttleScrollCallbackMS = 0; + _lastScrollDispatchTime = CACurrentMediaTime(); + _cachedChildFrames = [[NSMutableArray alloc] init]; [self addSubview:_scrollView]; } @@ -371,90 +368,120 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; [_scrollView zoomToRect:rect animated:animated]; } -#pragma mark - UIScrollViewDelegate methods +#pragma mark - ScrollView delegate -- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView -{ - [_scrollDispatcher scrollViewDidEndScrollingAnimation:_scrollView reactTag:[self reactTag]]; - if ([_nativeMainScrollDelegate respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)]) { - [_nativeMainScrollDelegate scrollViewDidEndScrollingAnimation:scrollView]; - } +#define RCT_SCROLL_EVENT_HANDLER(delegateMethod, eventName) \ +- (void)delegateMethod:(UIScrollView *)scrollView \ +{ \ + [_eventDispatcher sendScrollEventWithType:eventName reactTag:self.reactTag scrollView:scrollView userData:nil]; \ + if ([_nativeMainScrollDelegate respondsToSelector:_cmd]) { \ + [_nativeMainScrollDelegate delegateMethod:scrollView]; \ + } \ } +#define RCT_FORWARD_SCROLL_EVENT(call) \ +if ([_nativeMainScrollDelegate respondsToSelector:_cmd]) { \ + [_nativeMainScrollDelegate call]; \ +} + +RCT_SCROLL_EVENT_HANDLER(scrollViewDidEndScrollingAnimation, RCTScrollEventTypeEndDeceleration) +RCT_SCROLL_EVENT_HANDLER(scrollViewWillBeginDecelerating, RCTScrollEventTypeStartDeceleration) +RCT_SCROLL_EVENT_HANDLER(scrollViewDidEndDecelerating, RCTScrollEventTypeEndDeceleration) +RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove) + - (void)scrollViewDidScroll:(UIScrollView *)scrollView { - [_scrollDispatcher scrollViewDidScroll:_scrollView reactTag:[self reactTag]]; - if ([_nativeMainScrollDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) { - [_nativeMainScrollDelegate scrollViewDidScroll:scrollView]; - } -} + NSTimeInterval now = CACurrentMediaTime(); + NSTimeInterval throttleScrollCallbackSeconds = _throttleScrollCallbackMS / 1000.0; + + /** + * TODO: this logic looks wrong, and it may be because it is. Currently, if _throttleScrollCallbackMS + * is set to zero (the default), the "didScroll" event is only sent once per scroll, instead of repeatedly + * while scrolling as expected. However, if you "fix" that bug, ScrollView will generate repeated + * warnings, and behave strangely (ListView works fine however), so don't fix it unless you fix that too! + */ + if (_allowNextScrollNoMatterWhat || + (_throttleScrollCallbackMS != 0 && throttleScrollCallbackSeconds < (now - _lastScrollDispatchTime))) { + + // Calculate changed frames + NSMutableArray *updatedChildFrames = [[NSMutableArray alloc] init]; + [[_contentView reactSubviews] enumerateObjectsUsingBlock:^(UIView *subview, NSUInteger idx, BOOL *stop) { + + // Check if new or changed + CGRect newFrame = subview.frame; + BOOL frameChanged = NO; + if (_cachedChildFrames.count <= idx) { + frameChanged = YES; + [_cachedChildFrames addObject:[NSValue valueWithCGRect:newFrame]]; + } else if (!CGRectEqualToRect(newFrame, [_cachedChildFrames[idx] CGRectValue])) { + frameChanged = YES; + _cachedChildFrames[idx] = [NSValue valueWithCGRect:newFrame]; + } + + // Create JS frame object + if (frameChanged) { + [updatedChildFrames addObject: @{ + @"index": @(idx), + @"x": @(newFrame.origin.x), + @"y": @(newFrame.origin.y), + @"width": @(newFrame.size.width), + @"height": @(newFrame.size.height), + }]; + } + + }]; + + // If there are new frames, add them to event data + NSDictionary *userData = nil; + if (updatedChildFrames.count > 0) { + userData = @{@"updatedChildFrames": updatedChildFrames}; + } -- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView -{ - [_scrollDispatcher scrollViewWillBeginDecelerating:_scrollView reactTag:[self reactTag]]; - if ([_nativeMainScrollDelegate respondsToSelector:@selector(scrollViewWillBeginDecelerating:)]) { - [_nativeMainScrollDelegate scrollViewWillBeginDecelerating:scrollView]; - } -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView -{ - [_scrollDispatcher scrollViewDidEndDecelerating:_scrollView reactTag:[self reactTag]]; - if ([_nativeMainScrollDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) { - [_nativeMainScrollDelegate scrollViewDidEndDecelerating:scrollView]; + // Dispatch event + [_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove + reactTag:self.reactTag + scrollView:scrollView + userData:userData]; + // Update dispatch time + _lastScrollDispatchTime = now; + _allowNextScrollNoMatterWhat = NO; } + RCT_FORWARD_SCROLL_EVENT(scrollViewDidScroll:scrollView); } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { - if ([_nativeMainScrollDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) { - [_nativeMainScrollDelegate scrollViewWillBeginDragging:scrollView]; - } - [_scrollDispatcher scrollViewWillBeginDragging:_scrollView reactTag:[self reactTag]]; + _allowNextScrollNoMatterWhat = YES; // Ensure next scroll event is recorded, regardless of throttle + [_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeStart reactTag:self.reactTag scrollView:scrollView userData:nil]; + RCT_FORWARD_SCROLL_EVENT(scrollViewWillBeginDragging:scrollView); } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { - [_scrollDispatcher scrollViewWillEndDragging:_scrollView reactTag:[self reactTag]]; - if ([_nativeMainScrollDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { - [_nativeMainScrollDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; - } + [_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeEnd reactTag:self.reactTag scrollView:scrollView userData:nil]; + RCT_FORWARD_SCROLL_EVENT(scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset); } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { - if ([_nativeMainScrollDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]) { - [_nativeMainScrollDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; - } -} - -- (void)scrollViewDidZoom:(UIScrollView *)scrollView -{ - [_scrollDispatcher scrollViewDidScroll:_scrollView reactTag:[self reactTag]]; - if ([_nativeMainScrollDelegate respondsToSelector:@selector(scrollViewDidZoom:)]) { - [_nativeMainScrollDelegate scrollViewDidZoom:scrollView]; - } + RCT_FORWARD_SCROLL_EVENT(scrollViewDidEndDragging:scrollView willDecelerate:decelerate); } - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view { - [_scrollDispatcher scrollViewWillBeginDragging:_scrollView reactTag:[self reactTag]]; - if ([_nativeMainScrollDelegate respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) { - [_nativeMainScrollDelegate scrollViewWillBeginZooming:scrollView withView:view]; - } + [_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeStart reactTag:self.reactTag scrollView:scrollView userData:nil]; + RCT_FORWARD_SCROLL_EVENT(scrollViewWillBeginZooming:scrollView withView:view); } - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale { - [_scrollDispatcher scrollViewWillEndDragging:_scrollView reactTag:[self reactTag]]; - if ([_nativeMainScrollDelegate respondsToSelector:@selector(scrollViewDidEndZooming:withView:atScale:)]) { - [_nativeMainScrollDelegate scrollViewDidEndZooming:scrollView withView:view atScale:scale]; - } + [_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeEnd reactTag:self.reactTag scrollView:scrollView userData:nil]; + RCT_FORWARD_SCROLL_EVENT(scrollViewDidEndZooming:scrollView withView:view atScale:scale); } - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView { - if ([_nativeMainScrollDelegate respondsToSelector:@selector(scrollViewShouldScrollToTop:)]) { + if ([_nativeMainScrollDelegate respondsToSelector:_cmd]) { return [_nativeMainScrollDelegate scrollViewShouldScrollToTop:scrollView]; } return YES; @@ -467,11 +494,6 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; #pragma mark - Setters -- (void)setThrottleScrollCallbackMS:(NSUInteger)ms -{ - _scrollDispatcher.throttleScrollCallbackMS = ms; -} - - (CGSize)_calculateViewportSize { CGSize viewportSize = self.bounds.size; @@ -527,27 +549,24 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; { if (_contentSizeManuallySet) { _scrollView.contentSize = _contentSize; + } else if (!_contentView) { + _scrollView.contentSize = CGSizeZero; } else { - if (!_contentView) { - _scrollView.contentSize = CGSizeZero; - } else { - CGSize singleSubviewSize = _contentView.frame.size; - CGPoint singleSubviewPosition = _contentView.frame.origin; - CGSize fittedSize = CGSizeMake( - singleSubviewSize.width + singleSubviewPosition.x, - singleSubviewSize.height + singleSubviewPosition.y - ); - if (!CGSizeEqualToSize(_scrollView.contentSize, fittedSize)) { - // When contentSize is set manually, ScrollView internals will reset contentOffset to 0,0. Since - // we potentially set contentSize whenever anything in the ScrollView updates, we workaround this - // issue by manually adjusting contentOffset whenever this happens - CGPoint newOffset = [self calculateOffsetForContentSize:fittedSize]; - _scrollView.contentSize = fittedSize; - _scrollView.contentOffset = newOffset; - } - // when react makes changes to our - [_scrollView dockClosestSectionHeader]; + CGSize singleSubviewSize = _contentView.frame.size; + CGPoint singleSubviewPosition = _contentView.frame.origin; + CGSize fittedSize = { + singleSubviewSize.width + singleSubviewPosition.x, + singleSubviewSize.height + singleSubviewPosition.y + }; + if (!CGSizeEqualToSize(_scrollView.contentSize, fittedSize)) { + // When contentSize is set manually, ScrollView internals will reset contentOffset to 0,0. Since + // we potentially set contentSize whenever anything in the ScrollView updates, we workaround this + // issue by manually adjusting contentOffset whenever this happens + CGPoint newOffset = [self calculateOffsetForContentSize:fittedSize]; + _scrollView.contentSize = fittedSize; + _scrollView.contentOffset = newOffset; } + [_scrollView dockClosestSectionHeader]; } } diff --git a/ReactKit/Views/RCTScrollViewManager.m b/ReactKit/Views/RCTScrollViewManager.m index 8ebee1e50..1db32aae6 100644 --- a/ReactKit/Views/RCTScrollViewManager.m +++ b/ReactKit/Views/RCTScrollViewManager.m @@ -7,7 +7,7 @@ @implementation RCTScrollViewManager -- (UIView *)viewWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { return [[RCTScrollView alloc] initWithFrame:CGRectZero eventDispatcher:eventDispatcher]; } @@ -33,8 +33,8 @@ RCT_EXPORT_VIEW_PROPERTY(stickyHeaderIndices); RCT_EXPORT_VIEW_PROPERTY(throttleScrollCallbackMS); RCT_EXPORT_VIEW_PROPERTY(zoomScale); RCT_EXPORT_VIEW_PROPERTY(contentInset); -RCT_REMAP_VIEW_PROPERTY(scrollIndicatorInsets, scrollView.scrollIndicatorInsets); -RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffse); +RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets); +RCT_EXPORT_VIEW_PROPERTY(contentOffset); - (NSDictionary *)constantsToExport { diff --git a/ReactKit/Views/RCTShadowView.m b/ReactKit/Views/RCTShadowView.m index 90cd1dfe5..9a95e62da 100644 --- a/ReactKit/Views/RCTShadowView.m +++ b/ReactKit/Views/RCTShadowView.m @@ -183,17 +183,17 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st node->layout.should_update = false; _layoutLifecycle = RCTLayoutLifecycleComputed; - CGPoint absoluteTopLeft = (CGPoint){ + CGPoint absoluteTopLeft = { RCTRoundPixelValue(absolutePosition.x + node->layout.position[CSS_LEFT]), RCTRoundPixelValue(absolutePosition.y + node->layout.position[CSS_TOP]) }; - CGPoint absoluteBottomRight = (CGPoint){ + CGPoint absoluteBottomRight = { RCTRoundPixelValue(absolutePosition.x + node->layout.position[CSS_LEFT] + node->layout.dimensions[CSS_WIDTH]), RCTRoundPixelValue(absolutePosition.y + node->layout.position[CSS_TOP] + node->layout.dimensions[CSS_HEIGHT]) }; - CGRect frame = (CGRect){ + CGRect frame = { RCTRoundPixelValue(node->layout.position[CSS_LEFT]), RCTRoundPixelValue(node->layout.position[CSS_TOP]), RCTRoundPixelValue(absoluteBottomRight.x - absoluteTopLeft.x), diff --git a/ReactKit/Views/RCTStaticImageManager.m b/ReactKit/Views/RCTStaticImageManager.m index 620357b7d..86eac9a12 100644 --- a/ReactKit/Views/RCTStaticImageManager.m +++ b/ReactKit/Views/RCTStaticImageManager.m @@ -9,7 +9,7 @@ @implementation RCTStaticImageManager -- (UIView *)viewWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { return [[RCTStaticImage alloc] init]; } diff --git a/ReactKit/Views/RCTTextField.h b/ReactKit/Views/RCTTextField.h index 069ecbc03..45bacd8ac 100644 --- a/ReactKit/Views/RCTTextField.h +++ b/ReactKit/Views/RCTTextField.h @@ -2,7 +2,7 @@ #import -@class RCTJavaScriptEventDispatcher; +@class RCTEventDispatcher; @interface RCTTextField : UITextField @@ -10,6 +10,6 @@ @property (nonatomic, assign) BOOL autoCorrect; @property (nonatomic, assign) UIEdgeInsets paddingEdgeInsets; // TODO: contentInset -- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher; +- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTEventDispatcher *)eventDispatcher; @end diff --git a/ReactKit/Views/RCTTextField.m b/ReactKit/Views/RCTTextField.m index fafae3ddd..50f8170e7 100644 --- a/ReactKit/Views/RCTTextField.m +++ b/ReactKit/Views/RCTTextField.m @@ -3,14 +3,13 @@ #import "RCTTextField.h" #import "RCTConvert.h" -#import "RCTEventExtractor.h" -#import "RCTJavaScriptEventDispatcher.h" +#import "RCTEventDispatcher.h" #import "RCTUtils.h" #import "UIView+ReactKit.h" @implementation RCTTextField { - RCTJavaScriptEventDispatcher *_eventDispatcher; + RCTEventDispatcher *_eventDispatcher; NSMutableArray *_reactSubviews; BOOL _jsRequestingFirstResponder; } @@ -20,33 +19,41 @@ RCT_NOT_DESIGNATED_INITIALIZER(); } -- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTEventDispatcher *)eventDispatcher { - if (self = [super initWithFrame:frame]) { + if ((self = [super initWithFrame:frame])) { + _eventDispatcher = eventDispatcher; [self addTarget:self action:@selector(_textFieldDidChange) forControlEvents:UIControlEventEditingChanged]; + [self addTarget:self action:@selector(_textFieldBeginEditing) forControlEvents:UIControlEventEditingDidBegin]; [self addTarget:self action:@selector(_textFieldEndEditing) forControlEvents:UIControlEventEditingDidEnd]; [self addTarget:self action:@selector(_textFieldSubmitEditing) forControlEvents:UIControlEventEditingDidEndOnExit]; _reactSubviews = [[NSMutableArray alloc] init]; self.returnKeyType = UIReturnKeyDone; } - return self; } - (NSArray *)reactSubviews { + // TODO: do we support subviews of textfield in React? + // In any case, we should have a better approach than manually + // maintaining array in each view subclass like this return _reactSubviews; } - (void)removeReactSubview:(UIView *)subview { + // TODO: this is a bit broken - if the TextView inserts any of + // it's own views below or between React's, the indices won't match [_reactSubviews removeObject:subview]; [subview removeFromSuperview]; } - (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex { + // TODO: this is a bit broken - if the TextView inserts any of + // it's own views below or between React's, the indices won't match [_reactSubviews insertObject:view atIndex:atIndex]; [super insertSubview:view atIndex:atIndex]; } @@ -56,7 +63,6 @@ if (_caretHidden) { return CGRectZero; } - return [super caretRectForPosition:position]; } @@ -81,72 +87,45 @@ return self.autocorrectionType == UITextAutocorrectionTypeYes; } -- (void)_textFieldDidChange -{ - [self handleTextChange]; +#define RCT_TEXT_EVENT_HANDLER(delegateMethod, eventName) \ +- (void)delegateMethod \ +{ \ + [_eventDispatcher sendTextEventWithType:eventName \ + reactTag:self.reactTag \ + text:self.text]; \ } -- (void)_textFieldEndEditing -{ - NSDictionary *event = @{@"text": self.text, @"target": self.reactTag}; - [_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:[self reactTag] - type:RCTEventTextFieldEndEditing - nativeEventObj:event]]; -} +RCT_TEXT_EVENT_HANDLER(_textFieldDidChange, RCTTextEventTypeChange) +RCT_TEXT_EVENT_HANDLER(_textFieldBeginEditing, RCTTextEventTypeFocus) +RCT_TEXT_EVENT_HANDLER(_textFieldEndEditing, RCTTextEventTypeEnd) +RCT_TEXT_EVENT_HANDLER(_textFieldSubmitEditing, RCTTextEventTypeSubmit) -- (void)_textFieldSubmitEditing -{ - NSDictionary *event = @{@"text": self.text, @"target": self.reactTag}; - [_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:[self reactTag] - type:RCTEventTextFieldSubmitEditing - nativeEventObj:event]]; -} +// TODO: we should support shouldChangeTextInRect (see UITextFieldDelegate) - (BOOL)becomeFirstResponder { - _jsRequestingFirstResponder = YES; - BOOL wasPreviouslyResponder = [self isFirstResponder]; - BOOL ret = [super becomeFirstResponder]; - BOOL isTransitioningResponder = !wasPreviouslyResponder && ret; - if (isTransitioningResponder) { - [self handleTextFieldDidFocus]; - } + _jsRequestingFirstResponder = YES; // TODO: is this still needed? + BOOL result = [super becomeFirstResponder]; _jsRequestingFirstResponder = NO; - return ret; + return result; } - (BOOL)resignFirstResponder { - [self handleTextFieldWillBlur]; - return [super resignFirstResponder]; + BOOL result = [super resignFirstResponder]; + if (result) + { + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur + reactTag:self.reactTag + text:self.text]; + } + return result; } -// Prevent native from becoming first responder +// Prevent native from becoming first responder (TODO: why?) - (BOOL)canBecomeFirstResponder { return _jsRequestingFirstResponder; } -- (void)handleTextChange -{ - NSDictionary *event = @{@"text": self.text, @"target": self.reactTag}; - [_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:[self reactTag] - type:RCTEventChange - nativeEventObj:event]]; -} - -- (void)handleTextFieldDidFocus -{ - [_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:[self reactTag] - type:RCTEventTextFieldDidFocus - nativeEventObj:@{@"target":self.reactTag}]]; -} - -- (void)handleTextFieldWillBlur -{ - [_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:[self reactTag] - type:RCTEventTextFieldWillBlur - nativeEventObj:@{@"target":self.reactTag}]]; -} - @end diff --git a/ReactKit/Views/RCTTextFieldManager.m b/ReactKit/Views/RCTTextFieldManager.m index 699070677..cb037ec2e 100644 --- a/ReactKit/Views/RCTTextFieldManager.m +++ b/ReactKit/Views/RCTTextFieldManager.m @@ -8,7 +8,7 @@ @implementation RCTTextFieldManager -- (UIView *)viewWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { return [[RCTTextField alloc] initWithFrame:CGRectZero eventDispatcher:eventDispatcher]; } diff --git a/ReactKit/Views/RCTTextManager.m b/ReactKit/Views/RCTTextManager.m index a12fcd674..8a2f5bba6 100644 --- a/ReactKit/Views/RCTTextManager.m +++ b/ReactKit/Views/RCTTextManager.m @@ -13,7 +13,7 @@ @implementation RCTTextManager -- (UIView *)viewWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { UILabel *label = [[UILabel alloc] init]; label.numberOfLines = 0; diff --git a/ReactKit/Views/RCTUIActivityIndicatorViewManager.m b/ReactKit/Views/RCTUIActivityIndicatorViewManager.m index 616225bfd..7cc352e35 100644 --- a/ReactKit/Views/RCTUIActivityIndicatorViewManager.m +++ b/ReactKit/Views/RCTUIActivityIndicatorViewManager.m @@ -6,7 +6,7 @@ @implementation RCTUIActivityIndicatorViewManager -- (UIView *)viewWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { return [[UIActivityIndicatorView alloc] initWithFrame:CGRectZero]; } diff --git a/ReactKit/Views/RCTUIViewManager.m b/ReactKit/Views/RCTUIViewManager.m index f1493ab22..dbdcb5473 100644 --- a/ReactKit/Views/RCTUIViewManager.m +++ b/ReactKit/Views/RCTUIViewManager.m @@ -9,7 +9,7 @@ @implementation RCTUIViewManager -- (UIView *)viewWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { return [[UIView alloc] init]; } diff --git a/ReactKit/Views/RCTViewManager.m b/ReactKit/Views/RCTViewManager.m index f94dc912b..5acfa7332 100644 --- a/ReactKit/Views/RCTViewManager.m +++ b/ReactKit/Views/RCTViewManager.m @@ -6,7 +6,7 @@ @implementation RCTViewManager -- (UIView *)viewWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { return [[RCTView alloc] init]; } diff --git a/ReactKit/Views/RCTWrapperViewController.h b/ReactKit/Views/RCTWrapperViewController.h index e7cc2bac3..d8f22270a 100644 --- a/ReactKit/Views/RCTWrapperViewController.h +++ b/ReactKit/Views/RCTWrapperViewController.h @@ -2,7 +2,7 @@ #import -@class RCTJavaScriptEventDispatcher; +@class RCTEventDispatcher; @class RCTNavItem; @class RCTWrapperViewController; @@ -15,8 +15,8 @@ didMoveToNavigationController:(UINavigationController *)navigationController; @interface RCTWrapperViewController : UIViewController -- (instancetype)initWithContentView:(UIView *)contentView eventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher; -- (instancetype)initWithNavItem:(RCTNavItem *)navItem eventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher; +- (instancetype)initWithContentView:(UIView *)contentView eventDispatcher:(RCTEventDispatcher *)eventDispatcher; +- (instancetype)initWithNavItem:(RCTNavItem *)navItem eventDispatcher:(RCTEventDispatcher *)eventDispatcher; @property (nonatomic, readwrite, weak) id navigationListener; @property (nonatomic, strong, readwrite) RCTNavItem *navItem; diff --git a/ReactKit/Views/RCTWrapperViewController.m b/ReactKit/Views/RCTWrapperViewController.m index cb67ab8b7..b638d2b50 100644 --- a/ReactKit/Views/RCTWrapperViewController.m +++ b/ReactKit/Views/RCTWrapperViewController.m @@ -2,8 +2,7 @@ #import "RCTWrapperViewController.h" -#import "RCTEventExtractor.h" -#import "RCTJavaScriptEventDispatcher.h" +#import "RCTEventDispatcher.h" #import "RCTNavItem.h" #import "RCTUtils.h" #import "UIView+ReactKit.h" @@ -11,7 +10,7 @@ @implementation RCTWrapperViewController { UIView *_contentView; - RCTJavaScriptEventDispatcher *_eventDispatcher; + RCTEventDispatcher *_eventDispatcher; CGFloat _previousTopLayout; CGFloat _previousBottomLayout; } @@ -21,9 +20,9 @@ RCT_NOT_DESIGNATED_INITIALIZER(); } -- (instancetype)initWithContentView:(UIView *)contentView eventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (instancetype)initWithContentView:(UIView *)contentView eventDispatcher:(RCTEventDispatcher *)eventDispatcher { - if (self = [super initWithNibName:nil bundle:nil]) { + if ((self = [super initWithNibName:nil bundle:nil])) { _contentView = contentView; _eventDispatcher = eventDispatcher; self.automaticallyAdjustsScrollViewInsets = NO; @@ -31,9 +30,9 @@ return self; } -- (instancetype)initWithNavItem:(RCTNavItem *)navItem eventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher +- (instancetype)initWithNavItem:(RCTNavItem *)navItem eventDispatcher:(RCTEventDispatcher *)eventDispatcher { - if (self = [self initWithContentView:navItem eventDispatcher:eventDispatcher]) { + if ((self = [self initWithContentView:navItem eventDispatcher:eventDispatcher])) { _navItem = navItem; } return self; @@ -57,7 +56,7 @@ [[UIBarButtonItem alloc] initWithTitle:_navItem.rightButtonTitle style:UIBarButtonItemStyleDone target:self - action:@selector(_onRightButtonTapped:)]; + action:@selector(rightButtonTapped)]; } if (_navItem.backButtonTitle.length > 0) { @@ -95,20 +94,9 @@ [self.view addSubview:_contentView]; } -- (void)_onRightButtonTapped:(id)sender +- (void)rightButtonTapped { - RCTAssert(_navItem != nil, @""); - [self handleNavRightButtonTapped]; -} - -- (void)handleNavRightButtonTapped -{ - NSDictionary *nativeEvent = @{ - @"target":_navItem.reactTag - }; - [_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:[_navItem reactTag] - type:RCTEventNavRightButtonTap - nativeEventObj:nativeEvent]]; + [_eventDispatcher sendRawEventWithType:@"topNavRightButtonTap" body:@{@"target":_navItem.reactTag}]; } - (void)didMoveToParentViewController:(UIViewController *)parent