diff --git a/Examples/TicTacToe/TicTacToeApp.js b/Examples/TicTacToe/TicTacToeApp.js
index ff74aa2f2..73440acd2 100755
--- a/Examples/TicTacToe/TicTacToeApp.js
+++ b/Examples/TicTacToe/TicTacToeApp.js
@@ -9,6 +9,7 @@
var React = require('react-native');
var {
Bundler,
+ Image,
StyleSheet,
Text,
TouchableHighlight,
@@ -118,13 +119,22 @@ var Cell = React.createClass({
}
},
+ imageContents() {
+ switch (this.props.player) {
+ case 1:
+ return 'http://www.picgifs.com/alphabets/alphabets/children-5/alphabets-children-5-277623.gif';
+ case 2:
+ return 'http://www.picgifs.com/alphabets/alphabets/children-5/alphabets-children-5-730492.gif';
+ default:
+ return '';
+ }
+ },
+
render() {
return (
-
- {this.textContents()}
-
+
);
diff --git a/Libraries/ReactIOS/ReactIOSEventEmitter.js b/Libraries/ReactIOS/ReactIOSEventEmitter.js
index ab768c927..b539ff06e 100644
--- a/Libraries/ReactIOS/ReactIOSEventEmitter.js
+++ b/Libraries/ReactIOS/ReactIOSEventEmitter.js
@@ -169,12 +169,20 @@ var ReactIOSEventEmitter = merge(ReactEventEmitterMixin, {
var target = nativeEvent.target;
if (target !== null && target !== undefined) {
if (target < ReactIOSTagHandles.tagsStartAt) {
+ // When we get multiple touches at the same time, only the first touch
+ // actually has a view attached to it. The rest of the touches do not.
+ // This is presumably because iOS doesn't want to send touch events to
+ // two views for a single multi touch. Therefore this warning is only
+ // appropriate when it happens to the first touch. (hence jj === 0)
if (__DEV__) {
- warning(
- false,
- 'A view is reporting that a touch occured on tag zero.'
- );
+ if (jj === 0) {
+ warning(
+ false,
+ 'A view is reporting that a touch occured on tag zero.'
+ );
+ }
}
+ continue;
} else {
rootNodeID = NodeHandle.getRootNodeID(target);
}
diff --git a/Libraries/Utilities/createStrictShapeTypeChecker.js b/Libraries/Utilities/createStrictShapeTypeChecker.js
index 1c002950a..daaacb9b4 100644
--- a/Libraries/Utilities/createStrictShapeTypeChecker.js
+++ b/Libraries/Utilities/createStrictShapeTypeChecker.js
@@ -37,12 +37,14 @@ function createStrictShapeTypeChecker(shapeTypes) {
var allKeys = merge(props[propName], shapeTypes);
for (var key in allKeys) {
var checker = shapeTypes[key];
- invariant(
- checker,
- `Invalid props.${propName} key \`${key}\` supplied to \`${componentName}\`.` +
- `\nBad object: ` + JSON.stringify(props[propName], null, ' ') +
- `\nValid keys: ` + JSON.stringify(Object.keys(shapeTypes), null, ' ')
- );
+ if (!checker) {
+ invariant(
+ false,
+ `Invalid props.${propName} key \`${key}\` supplied to \`${componentName}\`.` +
+ `\nBad object: ` + JSON.stringify(props[propName], null, ' ') +
+ `\nValid keys: ` + JSON.stringify(Object.keys(shapeTypes), null, ' ')
+ );
+ }
var error = checker(propValue, key, componentName, location);
if (error) {
invariant(
diff --git a/ReactKit/Base/RCTBridge.h b/ReactKit/Base/RCTBridge.h
index ff1c5d7ad..1cb53fc0a 100644
--- a/ReactKit/Base/RCTBridge.h
+++ b/ReactKit/Base/RCTBridge.h
@@ -33,10 +33,9 @@ static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
*/
@interface RCTBridge : NSObject
-- (instancetype)initWithJavaScriptExecutor:(id)javaScriptExecutor
- javaScriptModulesConfig:(NSDictionary *)javaScriptModulesConfig;
+- (instancetype)initWithJavaScriptExecutor:(id)javaScriptExecutor;
-- (void)enqueueJSCall:(NSUInteger)moduleID methodID:(NSUInteger)methodID args:(NSArray *)args;
+- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete;
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
diff --git a/ReactKit/Base/RCTBridge.m b/ReactKit/Base/RCTBridge.m
index 6ea5a037a..3604b9dff 100644
--- a/ReactKit/Base/RCTBridge.m
+++ b/ReactKit/Base/RCTBridge.m
@@ -8,7 +8,6 @@
#import "RCTInvalidating.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
-#import "RCTModuleIDs.h"
#import "RCTUtils.h"
/**
@@ -36,19 +35,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldFlushDateMillis
};
-static NSString *RCTModuleName(Class moduleClass)
-{
- if ([moduleClass respondsToSelector:@selector(moduleName)]) {
-
- return [moduleClass moduleName];
-
- } else {
-
- // Default implementation, works in most cases
- return NSStringFromClass(moduleClass);
- }
-}
-
static NSDictionary *RCTNativeModuleClasses(void)
{
static NSMutableDictionary *modules;
@@ -73,7 +59,7 @@ static NSDictionary *RCTNativeModuleClasses(void)
}
// Get module name
- NSString *moduleName = RCTModuleName(cls);
+ NSString *moduleName = [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls);
// Check module name is unique
id existingClass = modules[moduleName];
@@ -90,23 +76,22 @@ static NSDictionary *RCTNativeModuleClasses(void)
@implementation RCTBridge
{
NSMutableDictionary *_moduleInstances;
- NSDictionary *_javaScriptModulesConfig;
+ NSMutableDictionary *_moduleIDLookup;
+ NSMutableDictionary *_methodIDLookup;
id _javaScriptExecutor;
}
static id _latestJSExecutor;
- (instancetype)initWithJavaScriptExecutor:(id)javaScriptExecutor
- javaScriptModulesConfig:(NSDictionary *)javaScriptModulesConfig
{
if ((self = [super init])) {
_javaScriptExecutor = javaScriptExecutor;
_latestJSExecutor = _javaScriptExecutor;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
- _javaScriptModulesConfig = javaScriptModulesConfig;
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);
- // Register modules
+ // Instantiate modules
_moduleInstances = [[NSMutableDictionary alloc] init];
[RCTNativeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
if (_moduleInstances[moduleName] == nil) {
@@ -118,6 +103,8 @@ static id _latestJSExecutor;
}
}];
+ _moduleIDLookup = [[NSMutableDictionary alloc] init];
+ _methodIDLookup = [[NSMutableDictionary alloc] init];
[self doneRegisteringModules];
}
@@ -168,10 +155,18 @@ static id _latestJSExecutor;
/**
* Like JS::call, for objective-c.
*/
-- (void)enqueueJSCall:(NSUInteger)moduleID methodID:(NSUInteger)methodID args:(NSArray *)args
+- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
{
- RCTAssertMainThread();
- [self _invokeRemoteJSModule:moduleID methodID:methodID args:args];
+ NSNumber *moduleID = _moduleIDLookup[moduleDotMethod];
+ RCTAssert(moduleID, @"Module '%@' not registered.",
+ [[moduleDotMethod componentsSeparatedByString:@"."] firstObject]);
+
+ NSNumber *methodID = _methodIDLookup[moduleDotMethod];
+ RCTAssert(methodID, @"Method '%@' not registered.", moduleDotMethod);
+
+ [self _invokeAndProcessModule:@"BatchedBridge"
+ method:@"callFunctionReturnFlushedQueue"
+ arguments:@[moduleID, methodID, args]];
}
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
@@ -217,13 +212,6 @@ static id _latestJSExecutor;
callback:processResponse];
}
-- (void)_invokeRemoteJSModule:(NSUInteger)moduleID methodID:(NSUInteger)methodID args:(NSArray *)args
-{
- [self _invokeAndProcessModule:@"BatchedBridge"
- method:@"callFunctionReturnFlushedQueue"
- arguments:@[@(moduleID), @(methodID), args]];
-}
-
/**
* TODO (#5906496): Have responses piggy backed on a round trip with ObjC->JS requests.
*/
@@ -333,21 +321,21 @@ static id _latestJSExecutor;
// invocation should not continue.
return;
}
-
- NSInvocation *invocation = [RCTBridge invocationForAdditionalArguments:methodArity];
-
+
// TODO: we should just store module instances by index, since that's how we look them up anyway
id target = strongSelf->_moduleInstances[moduleName];
RCTAssert(target != nil, @"No module found for name '%@'", moduleName);
- [invocation setArgument:&target atIndex:0];
-
SEL selector = method.selector;
+
+ NSMethodSignature *methodSignature = [target methodSignatureForSelector:selector];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
+ [invocation setArgument:&target atIndex:0];
[invocation setArgument:&selector atIndex:1];
// Retain used blocks until after invocation completes.
- NSMutableArray *blocks = [NSMutableArray array];
-
+ NS_VALID_UNTIL_END_OF_SCOPE NSMutableArray *blocks = [NSMutableArray array];
+
[params enumerateObjectsUsingBlock:^(id param, NSUInteger idx, BOOL *stop) {
if ([param isEqual:[NSNull null]]) {
param = nil;
@@ -356,8 +344,58 @@ static id _latestJSExecutor;
[blocks addObject:block];
param = block;
}
-
- [invocation setArgument:¶m atIndex:idx + 2];
+
+ NSUInteger argIdx = idx + 2;
+
+ BOOL shouldSet = YES;
+ const char *argumentType = [methodSignature getArgumentTypeAtIndex:argIdx];
+ switch (argumentType[0]) {
+ case ':':
+ if ([param isKindOfClass:[NSString class]]) {
+ SEL selector = NSSelectorFromString(param);
+ [invocation setArgument:&selector atIndex:argIdx];
+ shouldSet = NO;
+ }
+ break;
+
+ case '*':
+ if ([param isKindOfClass:[NSString class]]) {
+ const char *string = [param UTF8String];
+ [invocation setArgument:&string atIndex:argIdx];
+ shouldSet = NO;
+ }
+ break;
+
+#define CASE(_value, _type, _selector) \
+ case _value: \
+ if ([param respondsToSelector:@selector(_selector)]) { \
+ _type value = [param _selector]; \
+ [invocation setArgument:&value atIndex:argIdx]; \
+ shouldSet = NO; \
+ } \
+ break;
+
+ CASE('c', char, charValue)
+ CASE('C', unsigned char, unsignedCharValue)
+ CASE('s', short, shortValue)
+ CASE('S', unsigned short, unsignedShortValue)
+ CASE('i', int, intValue)
+ CASE('I', unsigned int, unsignedIntValue)
+ CASE('l', long, longValue)
+ CASE('L', unsigned long, unsignedLongValue)
+ CASE('q', long long, longLongValue)
+ CASE('Q', unsigned long long, unsignedLongLongValue)
+ CASE('f', float, floatValue)
+ CASE('d', double, doubleValue)
+ CASE('B', BOOL, boolValue)
+
+ default:
+ break;
+ }
+
+ if (shouldSet) {
+ [invocation setArgument:¶m atIndex:argIdx];
+ }
}];
@try {
@@ -366,10 +404,6 @@ static id _latestJSExecutor;
@catch (NSException *exception) {
RCTLogMustFix(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, target, params, exception);
}
- @finally {
- // Force `blocks` to remain alive until here.
- blocks = nil;
- }
});
return YES;
@@ -412,16 +446,71 @@ static id _latestJSExecutor;
return invocation;
}
+- (NSArray *)JSMethods
+{
+ NSMutableArray *methods = [[NSMutableArray alloc] init];
+
+ // Add globally used methods
+ [methods addObjectsFromArray:@[
+ @"Bundler.runApplication",
+ @"RCTEventEmitter.receiveEvent",
+ @"RCTEventEmitter.receiveTouches",
+ ]];
+
+ // NOTE: these methods are currently unused in the OSS project
+ // @"Dimensions.set",
+ // @"RCTDeviceEventEmitter.emit",
+ // @"RCTNativeAppEventEmitter.emit",
+ // @"ReactIOS.unmountComponentAtNodeAndRemoveContainer",
+
+ // Register individual methods from modules
+ for (Class cls in RCTNativeModuleClasses().allValues) {
+ if (RCTClassOverridesClassMethod(cls, @selector(JSMethods))) {
+ [methods addObjectsFromArray:[cls JSMethods]];
+ }
+ }
+
+ return methods;
+}
+
- (void)doneRegisteringModules
{
+ // TODO: everything in this method can actually be determined
+ // statically from just the class, and can therefore be cached
+ // in a dispatch_once instead of being repeated every time we
+ // reload or create a new RootView.
+
RCTAssertMainThread();
- RCTAssert(_javaScriptModulesConfig != nil, @"JS module config not loaded in APP");
- NSMutableDictionary *objectsToInject = [NSMutableDictionary dictionary];
-
- // Dictionary of { moduleName0: { moduleID: 0, methods: { methodName0: { methodID: 0, type: remote }, methodName1: { ... }, ... }, ... }
+ /**
+ * This constructs the remote modules configuration data structure,
+ * which represents the native modules and methods that will be called
+ * by JS. A numeric ID is assigned to each module and method, which will
+ * be used to communicate via the bridge. The structure of each
+ * module is as follows:
+ *
+ * "ModuleName1": {
+ * "moduleID": 0,
+ * "methods": {
+ * "methodName1": {
+ * "methodID": 0,
+ * "type": "remote"
+ * },
+ * "methodName2": {
+ * "methodID": 1,
+ * "type": "remote"
+ * },
+ * etc...
+ * },
+ * "constants": {
+ * ...
+ * }
+ * },
+ * etc...
+ */
+
NSUInteger moduleCount = RCTExportedMethodsByModule().count;
- NSMutableDictionary *moduleConfigs = [NSMutableDictionary dictionaryWithCapacity:RCTExportedMethodsByModule().count];
+ NSMutableDictionary *remoteModules = [NSMutableDictionary dictionaryWithCapacity:RCTExportedMethodsByModule().count];
for (NSUInteger i = 0; i < moduleCount; i++) {
NSString *moduleName = RCTExportedModuleNameAtSortedIndex(i);
NSArray *rawMethods = RCTExportedMethodsByModule()[moduleName];
@@ -433,35 +522,87 @@ static id _latestJSExecutor;
};
}];
- NSMutableDictionary *moduleConfig = [NSMutableDictionary dictionary];
- moduleConfig[@"moduleID"] = @(i);
- moduleConfig[@"methods"] = methods;
+ NSDictionary *module = @{
+ @"moduleID": @(i),
+ @"methods": methods
+ };
- id target = [_moduleInstances objectForKey:moduleName];
- if ([target respondsToSelector:@selector(constantsToExport)]) {
- moduleConfig[@"constants"] = [target constantsToExport];
+ id target = _moduleInstances[moduleName];
+ if (RCTClassOverridesClassMethod([target class], @selector(constantsToExport))) {
+ module = [module mutableCopy];
+ ((NSMutableDictionary *)module)[@"constants"] = [[target class] constantsToExport];
}
- moduleConfigs[moduleName] = moduleConfig;
+ remoteModules[moduleName] = module;
}
- NSDictionary *batchedBridgeConfig = @{
- @"remoteModuleConfig": moduleConfigs,
- @"localModulesConfig": _javaScriptModulesConfig
- };
-
- NSString *configJSON = RCTJSONStringify(batchedBridgeConfig, NULL);
- objectsToInject[@"__fbBatchedBridgeConfig"] = configJSON;
-
+
+ /**
+ * As above, but for local modules/methods, which represent JS classes
+ * and methods that will be called by the native code via the bridge.
+ * Structure is essentially the same as for remote modules:
+ *
+ * "ModuleName1": {
+ * "moduleID": 0,
+ * "methods": {
+ * "methodName1": {
+ * "methodID": 0,
+ * "type": "local"
+ * },
+ * "methodName2": {
+ * "methodID": 1,
+ * "type": "local"
+ * },
+ * etc...
+ * }
+ * },
+ * etc...
+ */
+
+ NSMutableDictionary *localModules = [[NSMutableDictionary alloc] init];
+ for (NSString *moduleDotMethod in [self JSMethods]) {
+
+ NSArray *parts = [moduleDotMethod componentsSeparatedByString:@"."];
+ RCTAssert(parts.count == 2, @"'%@' is not a valid JS method definition - expected 'Module.method' format.", moduleDotMethod);
+
+ // Add module if it doesn't already exist
+ NSString *moduleName = parts[0];
+ NSDictionary *module = localModules[moduleName];
+ if (!module) {
+ module = @{
+ @"moduleID": @(localModules.count),
+ @"methods": [[NSMutableDictionary alloc] init]
+ };
+ localModules[moduleName] = module;
+ }
+
+ // Add method if it doesn't already exist
+ NSString *methodName = parts[1];
+ NSMutableDictionary *methods = module[@"methods"];
+ if (!methods[methodName]) {
+ methods[methodName] = @{
+ @"methodID": @(methods.count),
+ @"type": @"local"
+ };
+ }
+
+ // Add module and method lookup
+ _moduleIDLookup[moduleDotMethod] = module[@"moduleID"];
+ _methodIDLookup[moduleDotMethod] = methods[methodName][@"methodID"];
+ }
+
+ /**
+ * Inject module data into JS context
+ */
+ NSString *configJSON = RCTJSONStringify(@{
+ @"remoteModuleConfig": remoteModules,
+ @"localModulesConfig": localModules
+ }, NULL);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
- [objectsToInject enumerateKeysAndObjectsUsingBlock:^(NSString *objectName, NSString *script, BOOL *stop) {
- [_javaScriptExecutor injectJSONText:script asGlobalObjectNamed:objectName callback:^(id err) {
- dispatch_semaphore_signal(semaphore);
- }];
+ [_javaScriptExecutor injectJSONText:configJSON asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:^(id err) {
+ dispatch_semaphore_signal(semaphore);
}];
- for (NSUInteger i = 0, count = objectsToInject.count; i < count; i++) {
- if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) {
- RCTLogMustFix(@"JavaScriptExecutor take too long to inject JSON object");
- }
+ if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) {
+ RCTLogMustFix(@"JavaScriptExecutor take too long to inject JSON object");
}
}
diff --git a/ReactKit/Base/RCTConvert.h b/ReactKit/Base/RCTConvert.h
index f22d395ef..efa35674e 100644
--- a/ReactKit/Base/RCTConvert.h
+++ b/ReactKit/Base/RCTConvert.h
@@ -41,6 +41,7 @@
+ (UIColor *)UIColor:(id)json;
+ (CGColorRef)CGColor:(id)json;
++ (CAKeyframeAnimation *)GIF:(id)json;
+ (UIImage *)UIImage:(id)json;
+ (CGImageRef)CGImage:(id)json;
@@ -56,4 +57,4 @@
+ (css_position_type_t)css_position_type_t:(id)json;
+ (css_wrap_type_t)css_wrap_type_t:(id)json;
-@end
\ No newline at end of file
+@end
diff --git a/ReactKit/Base/RCTConvert.m b/ReactKit/Base/RCTConvert.m
index 63673a678..cd91d0e6c 100644
--- a/ReactKit/Base/RCTConvert.m
+++ b/ReactKit/Base/RCTConvert.m
@@ -2,6 +2,9 @@
#import "RCTConvert.h"
+#import
+#import
+
#import "RCTLog.h"
CGFloat const RCTDefaultFontSize = 14;
@@ -426,6 +429,83 @@ RCT_STRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty"]
return [self UIColor:json].CGColor;
}
++ (CAKeyframeAnimation *)GIF:(id)json
+{
+ CGImageSourceRef imageSource;
+ if ([json isKindOfClass:[NSString class]]) {
+ NSString *path = json;
+ if (path.length == 0) {
+ return nil;
+ }
+
+ NSURL *fileURL = [path isAbsolutePath] ? [NSURL fileURLWithPath:path] : [[NSBundle mainBundle] URLForResource:path withExtension:nil];
+ imageSource = CGImageSourceCreateWithURL((CFURLRef)fileURL, NULL);
+ } else if ([json isKindOfClass:[NSData class]]) {
+ NSData *data = json;
+ if (data.length == 0) {
+ return nil;
+ }
+
+ imageSource = CGImageSourceCreateWithData((CFDataRef)data, NULL);
+ }
+
+ if (!UTTypeConformsTo(CGImageSourceGetType(imageSource), kUTTypeGIF)) {
+ CFRelease(imageSource);
+ return nil;
+ }
+
+ NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(imageSource, NULL);
+ NSUInteger loopCount = [properties[(id)kCGImagePropertyGIFDictionary][(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
+
+ size_t imageCount = CGImageSourceGetCount(imageSource);
+ NSTimeInterval duration = 0;
+ NSMutableArray *delays = [NSMutableArray arrayWithCapacity:imageCount];
+ NSMutableArray *images = [NSMutableArray arrayWithCapacity:imageCount];
+ for (size_t i = 0; i < imageCount; i++) {
+ CGImageRef image = CGImageSourceCreateImageAtIndex(imageSource, i, NULL);
+ NSDictionary *frameProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, i, NULL);
+ NSDictionary *frameGIFProperties = frameProperties[(id)kCGImagePropertyGIFDictionary];
+
+ const NSTimeInterval kDelayTimeIntervalDefault = 0.1;
+ NSNumber *delayTime = frameGIFProperties[(id)kCGImagePropertyGIFUnclampedDelayTime] ?: frameGIFProperties[(id)kCGImagePropertyGIFDelayTime];
+ if (delayTime == nil) {
+ if (i == 0) {
+ delayTime = @(kDelayTimeIntervalDefault);
+ } else {
+ delayTime = delays[i - 1];
+ }
+ }
+
+ const NSTimeInterval kDelayTimeIntervalMinimum = 0.02;
+ if (delayTime.floatValue < (float)kDelayTimeIntervalMinimum - FLT_EPSILON) {
+ delayTime = @(kDelayTimeIntervalDefault);
+ }
+
+ duration += delayTime.doubleValue;
+ delays[i] = delayTime;
+ images[i] = (__bridge_transfer id)image;
+ }
+
+ CFRelease(imageSource);
+
+ NSMutableArray *keyTimes = [NSMutableArray arrayWithCapacity:delays.count];
+ NSTimeInterval runningDuration = 0;
+ for (NSNumber *delayNumber in delays) {
+ [keyTimes addObject:@(runningDuration / duration)];
+ runningDuration += delayNumber.doubleValue;
+ }
+
+ [keyTimes addObject:@1.0];
+
+ CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
+ animation.calculationMode = kCAAnimationDiscrete;
+ animation.repeatCount = loopCount == 0 ? HUGE_VALF : loopCount;
+ animation.keyTimes = keyTimes;
+ animation.values = images;
+ animation.duration = duration;
+ return animation;
+}
+
+ (UIImage *)UIImage:(id)json
{
if (![json isKindOfClass:[NSString class]]) {
diff --git a/ReactKit/Base/RCTEventDispatcher.h b/ReactKit/Base/RCTEventDispatcher.h
index 3f9e8b2a3..d06994959 100644
--- a/ReactKit/Base/RCTEventDispatcher.h
+++ b/ReactKit/Base/RCTEventDispatcher.h
@@ -33,9 +33,11 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
- (instancetype)initWithBridge:(RCTBridge *)bridge;
/**
- * Send an arbitrary event type
+ * Send a named event. For most purposes, use the an
+ * event type of RCTEventTypeDefault, the other types
+ * are used internally by the React framework.
*/
-- (void)sendRawEventWithType:(NSString *)eventType body:(NSDictionary *)body;
+- (void)sendEventWithName:(NSString *)name body:(NSDictionary *)body;
/**
* Send an array of touch events
diff --git a/ReactKit/Base/RCTEventDispatcher.m b/ReactKit/Base/RCTEventDispatcher.m
index a7484fab0..fec3e32ae 100644
--- a/ReactKit/Base/RCTEventDispatcher.m
+++ b/ReactKit/Base/RCTEventDispatcher.m
@@ -3,7 +3,6 @@
#import "RCTEventDispatcher.h"
#import "RCTBridge.h"
-#import "RCTModuleIDs.h"
#import "UIView+ReactKit.h"
@implementation RCTEventDispatcher
@@ -19,39 +18,13 @@
return self;
}
-- (NSArray *)touchEvents
+- (void)sendEventWithName:(NSString *)name body:(NSDictionary *)body
{
- 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");
+ @"Event body dictionary must include a 'target' property containing a react tag");
- [_bridge enqueueJSCall:RCTModuleIDReactIOSEventEmitter
- methodID:RCTEventEmitterReceiveEvent
- args:@[body[@"target"], eventType, body]];
+ [_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent"
+ args:@[body[@"target"], name, body]];
}
/**
@@ -69,30 +42,32 @@
touches:(NSArray *)touches
changedIndexes:(NSArray *)changedIndexes
{
+ static NSString *events[] = {
+ @"topTouchStart",
+ @"topTouchMove",
+ @"topTouchEnd",
+ @"topTouchCancel",
+ };
+
RCTAssert(touches.count, @"No touches in touchEventArgsForOrderedTouches");
- [_bridge enqueueJSCall:RCTModuleIDReactIOSEventEmitter
- methodID:RCTEventEmitterReceiveTouches
- args:@[[self touchEvents][type], touches, changedIndexes]];
+ [_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
+ args:@[events[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",
- ];
- });
+ static NSString *events[] = {
+ @"topFocus",
+ @"topBlur",
+ @"topChange",
+ @"topSubmitEditing",
+ @"topEndEditing",
+ };
- [self sendRawEventWithType:events[type] body:@{
+ [self sendEventWithName:events[type] body:@{
@"text": text,
@"target": reactTag
}];
@@ -111,18 +86,14 @@
scrollView:(UIScrollView *)scrollView
userData:(NSDictionary *)userData
{
- static NSArray *events;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- events = @[
- @"topScrollBeginDrag",
- @"topScroll",
- @"topScrollEndDrag",
- @"topMomentumScrollBegin",
- @"topMomentumScrollEnd",
- @"topScrollAnimationEnd",
- ];
- });
+ static NSString *events[] = {
+ @"topScrollBeginDrag",
+ @"topScroll",
+ @"topScrollEndDrag",
+ @"topMomentumScrollBegin",
+ @"topMomentumScrollEnd",
+ @"topScrollAnimationEnd",
+ };
NSDictionary *body = @{
@"contentOffset": @{
@@ -147,7 +118,7 @@
body = mutableBody;
}
- [self sendRawEventWithType:events[type] body:body];
+ [self sendEventWithName:events[type] body:body];
}
@end
diff --git a/ReactKit/Base/RCTExport.h b/ReactKit/Base/RCTExport.h
index f78370b9c..d4bd1772b 100644
--- a/ReactKit/Base/RCTExport.h
+++ b/ReactKit/Base/RCTExport.h
@@ -26,6 +26,7 @@ extern NSString *RCTExportedModuleNameAtSortedIndex(NSUInteger index);
extern NSDictionary *RCTExportedMethodsByModule(void);
extern BOOL RCTSetProperty(id target, NSString *keypath, id value);
+extern BOOL RCTCopyProperty(id target, id source, NSString *keypath);
extern BOOL RCTCallSetter(id target, SEL setter, id value);
/* ------------------------------------------------------------------- */
@@ -66,9 +67,22 @@ _RCTExportSectionName))) static const RCTExportEntry __rct_export_entry__ = { __
/**
* Injects constants into JS. These constants are made accessible via
- * NativeModules.moduleName.X.
+ * NativeModules.moduleName.X. Note that this method is not inherited when you
+ * subclass a module, and you should not call [super constantsToExport] when
+ * implementing it.
*/
-- (NSDictionary *)constantsToExport;
++ (NSDictionary *)constantsToExport;
+
+/**
+ * An array of JavaScript methods that the module will call via the
+ * -[RCTBridge enqueueJSCall:args:] method. Each method should be specified
+ * as a string of the form "JSModuleName.jsMethodName". Attempting to call a
+ * method that has not been registered will result in an error. If a method
+ * has already been regsistered by another module, it is not necessary to
+ * register it again, but it is good pratice. Registering the same method
+ * more than once is silently ignored and will not result in an error.
+ */
++ (NSArray *)JSMethods;
/**
* Notifies the module that a batch of JS method invocations has just completed.
@@ -84,9 +98,18 @@ _RCTExportSectionName))) static const RCTExportEntry __rct_export_entry__ = { __
@protocol RCTNativeViewModule
/**
- * This method instantiates a native view to be managed by the module.
+ * Designated initializer for view modules. The event dispatched can either be
+ * used directly by the module, or passed on to instantiated views for event handling.
*/
-- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
+- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
+
+/**
+ * This method instantiates a native view to be managed by the module. The method
+ * will be called many times, and should return a fresh instance each time. The
+ * view module MUST NOT cache the returned view and return the same instance
+ * for subsequent calls.
+ */
+- (UIView *)view;
@optional
@@ -98,7 +121,8 @@ _RCTExportSectionName))) static const RCTExportEntry __rct_export_entry__ = { __
/**
* This method instantiates a shadow view to be managed by the module. If omitted,
- * an ordinary RCTShadowView instance will be created.
+ * an ordinary RCTShadowView instance will be created. As with the -view method,
+ * the -shadowView method should return a fresh instance each time it is called.
*/
- (RCTShadowView *)shadowView;
@@ -164,8 +188,11 @@ RCT_REMAP_VIEW_PROPERTY(name, name)
* }
* }
* }
+ *
+ * Note that this method is not inherited when you subclass a view module, and
+ * you should not call [super customBubblingEventTypes] when implementing it.
*/
-- (NSDictionary *)customBubblingEventTypes;
++ (NSDictionary *)customBubblingEventTypes;
/**
* Returns a dictionary of config data passed to JS that defines eligible events
@@ -177,14 +204,19 @@ RCT_REMAP_VIEW_PROPERTY(name, name)
* @"registrationName": @"onTwirl"
* }
* }
+ *
+ * Note that this method is not inherited when you subclass a view module, and
+ * you should not call [super customDirectEventTypes] when implementing it.
*/
-- (NSDictionary *)customDirectEventTypes;
++ (NSDictionary *)customDirectEventTypes;
/**
* Injects constants into JS. These constants are made accessible via
- * NativeModules.moduleName.X.
+ * NativeModules.moduleName.X. Note that this method is not inherited when you
+ * subclass a view module, and you should not call [super constantsToExport]
+ * when implementing it.
*/
-- (NSDictionary *)constantsToExport;
++ (NSDictionary *)constantsToExport;
/**
* To deprecate, hopefully
diff --git a/ReactKit/Base/RCTExport.m b/ReactKit/Base/RCTExport.m
index 23e6b35f4..be206cba4 100644
--- a/ReactKit/Base/RCTExport.m
+++ b/ReactKit/Base/RCTExport.m
@@ -349,6 +349,36 @@ BOOL RCTSetProperty(id target, NSString *keypath, id value)
return YES;
}
+BOOL RCTCopyProperty(id target, id source, NSString *keypath)
+{
+ // Split keypath
+ NSArray *parts = [keypath componentsSeparatedByString:@"."];
+ NSString *key = [parts lastObject];
+ for (NSUInteger i = 0; i < parts.count - 1; i++) {
+ source = [source valueForKey:parts[i]];
+ target = [target valueForKey:parts[i]];
+ if (!source || !target) {
+ return NO;
+ }
+ }
+
+ // Check class for property definition
+ if (!class_getProperty([source class], [key UTF8String])) {
+ // Check if setter exists
+ SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
+ [[key substringToIndex:1] uppercaseString],
+ [key substringFromIndex:1]]);
+
+ if (![source respondsToSelector:setter]
+ || ![target respondsToSelector:setter]) {
+ return NO;
+ }
+ }
+
+ [target setValue:[source valueForKey:key] forKey:key];
+ return YES;
+}
+
BOOL RCTCallSetter(id target, SEL setter, id value)
{
// Get property name
diff --git a/ReactKit/Base/RCTImageDownloader.h b/ReactKit/Base/RCTImageDownloader.h
index 241846cdd..b525ea1a7 100644
--- a/ReactKit/Base/RCTImageDownloader.h
+++ b/ReactKit/Base/RCTImageDownloader.h
@@ -2,12 +2,16 @@
#import
+typedef void (^RCTDataDownloadBlock)(NSData *data, NSError *error);
typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
@interface RCTImageDownloader : NSObject
+ (instancetype)sharedInstance;
+- (id)downloadDataForURL:(NSURL *)url
+ block:(RCTDataDownloadBlock)block;
+
- (id)downloadImageForURL:(NSURL *)url
size:(CGSize)size
scale:(CGFloat)scale
diff --git a/ReactKit/Base/RCTImageDownloader.m b/ReactKit/Base/RCTImageDownloader.m
index f3f996fbe..984e6cf6b 100644
--- a/ReactKit/Base/RCTImageDownloader.m
+++ b/ReactKit/Base/RCTImageDownloader.m
@@ -18,6 +18,20 @@
return sharedInstance;
}
+- (id)downloadDataForURL:(NSURL *)url
+ block:(RCTDataDownloadBlock)block
+{
+ NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
+ // Dispatch back to main thread
+ dispatch_async(dispatch_get_main_queue(), ^{
+ block(data, error);
+ });
+ }];
+
+ [task resume];
+ return task;
+}
+
- (id)downloadImageForURL:(NSURL *)url
size:(CGSize)size
scale:(CGFloat)scale
diff --git a/ReactKit/Base/RCTModuleIDs.h b/ReactKit/Base/RCTModuleIDs.h
deleted file mode 100644
index f03f69002..000000000
--- a/ReactKit/Base/RCTModuleIDs.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2004-present Facebook. All Rights Reserved.
-
-#import
-
-/**
- * All of this will be replaced with an auto-generated bridge.
- */
-
-typedef NS_ENUM(NSUInteger, RCTJSModuleIDs) {
- RCTModuleIDReactIOSEventEmitter,
- RCTModuleIDJSTimers, // JS timer tracking module
- RCTModuleIDReactIOS,
- RCTModuleIDBundler,
- RCTModuleIDDimensions,
- RCTModuleIDDeviceEventEmitter,
- RCTModuleIDNativeAppEventEmitter,
-};
-
-/**
- * JS module `RCTIOSEventEmitter`.
- */
-typedef NS_ENUM(NSUInteger, RCTEventEmitterRemoteMethodIDs) {
- RCTEventEmitterReceiveEvent = 0,
- RCTEventEmitterReceiveTouches
-};
-
-typedef NS_ENUM(NSUInteger, RCTKeyCode) {
- RCTKeyCodeBackspace = 8,
- RCTKeyCodeReturn = 13,
-};
-
-/**
- * JS timer tracking module.
- */
-typedef NS_ENUM(NSUInteger, RCTJSTimersMethodIDs) {
- RCTJSTimersCallTimers = 0
-};
-
-typedef NS_ENUM(NSUInteger, RCTReactIOSMethodIDs) {
- RCTReactIOSUnmountComponentAtNodeAndRemoveContainer = 0,
-};
-
-typedef NS_ENUM(NSUInteger, RCTBundlerMethodIDs) {
- RCTBundlerRunApplication = 0
-};
-
-typedef NS_ENUM(NSUInteger, RCTDimensionsMethodIDs) {
- RCTDimensionsSet = 0
-};
-
-typedef NS_ENUM(NSUInteger, RCTDeviceEventEmitterMethodIDs) {
- RCTDeviceEventEmitterEmit = 0
-};
-
-@interface RCTModuleIDs : NSObject
-
-+ (NSDictionary *)config;
-
-@end
-
diff --git a/ReactKit/Base/RCTModuleIDs.m b/ReactKit/Base/RCTModuleIDs.m
deleted file mode 100644
index e83707906..000000000
--- a/ReactKit/Base/RCTModuleIDs.m
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2004-present Facebook. All Rights Reserved.
-
-#import "RCTModuleIDs.h"
-
-@implementation RCTModuleIDs
-
-/**
- * Configures invocations from IOS -> JS. Simply passes the name of the key in
- * the configuration object `require('ReactIOSEventEmitter')`.
- */
-+ (NSDictionary *)config
-{
- return @{
- @"Dimensions": @{
- @"moduleID": @(RCTModuleIDDimensions),
- @"methods": @{
- @"set": @{
- @"methodID": @(RCTDimensionsSet),
- @"type": @"local"
- },
- }
- },
-
- @"RCTDeviceEventEmitter": @{
- @"moduleID": @(RCTModuleIDDeviceEventEmitter),
- @"methods": @{
- @"emit": @{
- @"methodID": @(RCTDeviceEventEmitterEmit),
- @"type": @"local"
- },
- }
- },
-
- @"RCTEventEmitter": @{
- @"moduleID": @(RCTModuleIDReactIOSEventEmitter),
- @"methods": @{
- @"receiveEvent": @{
- @"methodID": @(RCTEventEmitterReceiveEvent),
- @"type": @"local"
- },
- @"receiveTouches": @{
- @"methodID": @(RCTEventEmitterReceiveTouches),
- @"type": @"local"
- },
- }
- },
-
- @"RCTNativeAppEventEmitter": @{
- @"moduleID": @(RCTModuleIDNativeAppEventEmitter),
- @"methods": @{
- @"emit": @{
- @"methodID": @(RCTDeviceEventEmitterEmit),
- @"type": @"local"
- },
- }
- },
-
- @"RCTJSTimers": @{
- @"moduleID": @(RCTModuleIDJSTimers),
- @"methods": @{
- // Last argument is the callback.
- @"callTimers": @{
- @"methodID": @(RCTJSTimersCallTimers),
- @"type": @"local"
- },
- }
- },
-
- @"ReactIOS": @{
- @"moduleID": @(RCTModuleIDReactIOS),
- @"methods": @{
- @"unmountComponentAtNodeAndRemoveContainer": @{
- @"methodID": @(RCTReactIOSUnmountComponentAtNodeAndRemoveContainer),
- @"type": @"local"
- },
- }
- },
-
- @"Bundler": @{
- @"moduleID": @(RCTModuleIDBundler),
- @"methods": @{
- @"runApplication": @{
- @"methodID": @(RCTBundlerRunApplication),
- @"type": @"local"
- }
- }
- }
- };
-}
-
-@end
-
diff --git a/ReactKit/Base/RCTRootView.m b/ReactKit/Base/RCTRootView.m
index a5e9b56f1..a6ec65038 100644
--- a/ReactKit/Base/RCTRootView.m
+++ b/ReactKit/Base/RCTRootView.m
@@ -6,11 +6,11 @@
#import "RCTContextExecutor.h"
#import "RCTEventDispatcher.h"
#import "RCTJavaScriptAppEngine.h"
-#import "RCTModuleIDs.h"
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "RCTViewManager.h"
+#import "RCTWebViewExecutor.h"
#import "UIView+ReactKit.h"
#import "RCTKeyCommands.h"
@@ -23,6 +23,8 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
RCTTouchHandler *_touchHandler;
}
+static BOOL _useWebExec;
+
+ (void)initialize
{
@@ -34,6 +36,13 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
action:^(UIKeyCommand *command) {
[self reloadAll];
}];
+ // Cmd-D reloads using the web view executor, allows attaching from Safari dev tools.
+ [[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d"
+ modifierFlags:UIKeyModifierCommand
+ action:^(UIKeyCommand *command) {
+ _useWebExec = YES;
+ [self reloadAll];
+ }];
#endif
@@ -98,8 +107,8 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
@"rootTag": self.reactTag ?: @0,
@"initialProps": self.initialProperties ?: @{},
};
- [_bridge enqueueJSCall:RCTModuleIDBundler
- methodID:RCTBundlerRunApplication
+
+ [_bridge enqueueJSCall:@"Bundler.runApplication"
args:@[moduleName, appParameters]];
}
}
@@ -123,9 +132,13 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
[_executor invalidate];
[_bridge invalidate];
- _executor = [[RCTContextExecutor alloc] init];
- _bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor
- javaScriptModulesConfig:[RCTModuleIDs config]];
+ if (!_useWebExec) {
+ _executor = [[RCTContextExecutor alloc] init];
+ } else {
+ _executor = [[RCTWebViewExecutor alloc] init];
+ }
+
+ _bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor];
_appEngine = [[RCTJavaScriptAppEngine alloc] initWithBridge:_bridge];
_touchHandler = [[RCTTouchHandler alloc] initWithEventDispatcher:_bridge.eventDispatcher rootView:self];
@@ -165,22 +178,4 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
[[NSNotificationCenter defaultCenter] postNotificationName:RCTRootViewReloadNotification object:nil];
}
-#pragma mark - Key commands
-
-- (NSArray *)keyCommands
-{
- return @[
-
- // Reload
- [UIKeyCommand keyCommandWithInput:@"r"
- modifierFlags:UIKeyModifierCommand
- action:@selector(reload)]
- ];
-}
-
-- (BOOL)canBecomeFirstResponder
-{
- return YES;
-}
-
@end
diff --git a/ReactKit/Base/RCTSparseArray.h b/ReactKit/Base/RCTSparseArray.h
index 4dd7bceba..e7a999a08 100644
--- a/ReactKit/Base/RCTSparseArray.h
+++ b/ReactKit/Base/RCTSparseArray.h
@@ -4,9 +4,8 @@
@interface RCTSparseArray : NSObject
-- (instancetype)init;
-- (instancetype)initWithCapacity:(NSUInteger)capacity;
-- (instancetype)initWithSparseArray:(RCTSparseArray *)sparseArray;
+- (instancetype)initWithCapacity:(NSUInteger)capacity NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithSparseArray:(RCTSparseArray *)sparseArray NS_DESIGNATED_INITIALIZER;
+ (instancetype)sparseArray;
+ (instancetype)sparseArrayWithCapacity:(NSUInteger)capacity;
diff --git a/ReactKit/Base/RCTSparseArray.m b/ReactKit/Base/RCTSparseArray.m
index ca88d2db6..12c7386cd 100644
--- a/ReactKit/Base/RCTSparseArray.m
+++ b/ReactKit/Base/RCTSparseArray.m
@@ -28,14 +28,6 @@
return self;
}
-- (instancetype)initWithStorage:(NSDictionary *)storage
-{
- if ((self = [super init])) {
- _storage = [storage copy];
- }
- return self;
-}
-
+ (instancetype)sparseArray
{
return [[self alloc] init];
diff --git a/ReactKit/Base/RCTTouchHandler.m b/ReactKit/Base/RCTTouchHandler.m
index 09071bc3e..8b4c43e7c 100644
--- a/ReactKit/Base/RCTTouchHandler.m
+++ b/ReactKit/Base/RCTTouchHandler.m
@@ -11,6 +11,9 @@
#import "RCTUtils.h"
#import "UIView+ReactKit.h"
+// TODO: this class behaves a lot like a module, and could be implemented as a
+// module if we were to assume that modules and RootViews had a 1:1 relationship
+
@implementation RCTTouchHandler
{
__weak UIView *_rootView;
@@ -27,11 +30,6 @@
NSMutableArray *_touchViews;
}
-- (instancetype)init
-{
- RCT_NOT_DESIGNATED_INITIALIZER();
-}
-
- (instancetype)initWithTarget:(id)target action:(SEL)action
{
RCT_NOT_DESIGNATED_INITIALIZER();
diff --git a/ReactKit/Base/RCTUtils.h b/ReactKit/Base/RCTUtils.h
index 35bb43f56..3011d5beb 100644
--- a/ReactKit/Base/RCTUtils.h
+++ b/ReactKit/Base/RCTUtils.h
@@ -20,8 +20,9 @@ id RCTJSONParse(NSString *jsonString, NSError **error);
// Get MD5 hash of a string
NSString *RCTMD5Hash(NSString *string);
-// Get screen scale in a thread-safe way
+// Get screen metrics in a thread-safe way
CGFloat RCTScreenScale(void);
+CGSize RCTScreenSize(void);
// Round float coordinates to nearest whole screen pixel (not point)
CGFloat RCTRoundPixelValue(CGFloat value);
@@ -34,3 +35,7 @@ NSTimeInterval RCTTGetAbsoluteTime(void);
// Method swizzling
void RCTSwapClassMethods(Class cls, SEL original, SEL replacement);
void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement);
+
+// Module subclass support
+BOOL RCTClassOverridesClassMethod(Class cls, SEL selector);
+BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector);
diff --git a/ReactKit/Base/RCTUtils.m b/ReactKit/Base/RCTUtils.m
index a6c241207..615c5235e 100644
--- a/ReactKit/Base/RCTUtils.m
+++ b/ReactKit/Base/RCTUtils.m
@@ -35,7 +35,7 @@ NSString *RCTMD5Hash(NSString *string)
CGFloat RCTScreenScale()
{
- static CGFloat scale = -1;
+ static CGFloat scale;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (![NSThread isMainThread]) {
@@ -50,6 +50,23 @@ CGFloat RCTScreenScale()
return scale;
}
+CGSize RCTScreenSize()
+{
+ static CGSize size;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ if (![NSThread isMainThread]) {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ size = [UIScreen mainScreen].bounds.size;
+ });
+ } else {
+ size = [UIScreen mainScreen].bounds.size;
+ }
+ });
+
+ return size;
+}
+
CGFloat RCTRoundPixelValue(CGFloat value)
{
CGFloat scale = RCTScreenScale();
@@ -119,4 +136,25 @@ void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement)
{
method_exchangeImplementations(originalMethod, replacementMethod);
}
-}
\ No newline at end of file
+}
+
+BOOL RCTClassOverridesClassMethod(Class cls, SEL selector)
+{
+ return RCTClassOverridesInstanceMethod(object_getClass(cls), selector);
+}
+
+BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector)
+{
+ unsigned int numberOfMethods;
+ Method *methods = class_copyMethodList(cls, &numberOfMethods);
+ for (unsigned int i = 0; i < numberOfMethods; i++)
+ {
+ if (method_getName(methods[i]) == selector)
+ {
+ free(methods);
+ return YES;
+ }
+ }
+ return NO;
+}
+
diff --git a/ReactKit/Executors/RCTWebViewExecutor.m b/ReactKit/Executors/RCTWebViewExecutor.m
index 307661799..e774089bd 100644
--- a/ReactKit/Executors/RCTWebViewExecutor.m
+++ b/ReactKit/Executors/RCTWebViewExecutor.m
@@ -41,6 +41,11 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
return self;
}
+- (id)init
+{
+ return [self initWithWebView:[[UIWebView alloc] init]];
+}
+
- (BOOL)isValid
{
return _webView != nil;
@@ -98,7 +103,13 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
sourceURL:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
- RCTAssertMainThread();
+ if (![NSThread isMainThread]) {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [self executeApplicationScript:script sourceURL:url onComplete:onComplete];
+ });
+ return;
+ }
+
RCTAssert(onComplete != nil, @"");
_onApplicationScriptLoaded = onComplete;
@@ -137,26 +148,12 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
*/
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
{
- if (![NSThread isMainThread]) {
- [self performSelectorOnMainThread:@selector(executeBlockOnJavaScriptQueue:)
- withObject:block
- waitUntilDone:YES];
- } else {
- [self performSelector:@selector(_onMainThreadExecuteBlockAfterDelay:)
- withObject:block afterDelay:0.001 // This can't be zero!
- inModes:@[NSDefaultRunLoopMode, UITrackingRunLoopMode]];
- }
-}
+ dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC);
-/**
- * This timing delay is needed to avoid crashes in WebKit when setting a
- * breakpoint or `debugger` statement and debugging via the remote Safari
- * inspector.
- */
-- (void)_onMainThreadExecuteBlockAfterDelay:(dispatch_block_t)block
-{
- RCTAssertMainThread();
- block();
+ dispatch_after(when, dispatch_get_main_queue(), ^{
+ RCTAssertMainThread();
+ block();
+ });
}
/**
diff --git a/ReactKit/Modules/RCTTiming.m b/ReactKit/Modules/RCTTiming.m
index cc45fdbc2..d01b07509 100644
--- a/ReactKit/Modules/RCTTiming.m
+++ b/ReactKit/Modules/RCTTiming.m
@@ -4,7 +4,6 @@
#import "RCTBridge.h"
#import "RCTLog.h"
-#import "RCTModuleIDs.h"
#import "RCTSparseArray.h"
#import "RCTUtils.h"
@@ -55,6 +54,11 @@
id _updateTimer;
}
++ (NSArray *)JSMethods
+{
+ return @[@"RCTJSTimers.callTimers"];
+}
+
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
@@ -143,7 +147,7 @@
// call timers that need to be called
if ([timersToCall count] > 0) {
- [_bridge enqueueJSCall:RCTModuleIDJSTimers methodID:RCTJSTimersCallTimers args:@[timersToCall]];
+ [_bridge enqueueJSCall:@"RCTJSTimers.callTimers" args:@[timersToCall]];
}
}
@@ -155,14 +159,14 @@
* Date.now() from JS and then subtracting that from the current time here.
*/
- (void)createTimer:(NSNumber *)callbackID
- duration:(NSNumber *)jsDuration
- jsSchedulingTime:(NSNumber *)jsSchedulingTime
- repeats:(NSNumber *)repeats
+ duration:(double)jsDuration
+ jsSchedulingTime:(double)jsSchedulingTime
+ repeats:(BOOL)repeats
{
RCT_EXPORT();
- NSTimeInterval interval = jsDuration.doubleValue / 1000;
- NSTimeInterval jsCreationTimeSinceUnixEpoch = jsSchedulingTime.doubleValue / 1000;
+ NSTimeInterval interval = jsDuration / 1000;
+ NSTimeInterval jsCreationTimeSinceUnixEpoch = jsSchedulingTime / 1000;
NSTimeInterval currentTimeSinceUnixEpoch = [[NSDate date] timeIntervalSince1970];
NSTimeInterval jsSchedulingOverhead = currentTimeSinceUnixEpoch - jsCreationTimeSinceUnixEpoch;
if (jsSchedulingOverhead < 0) {
diff --git a/ReactKit/Modules/RCTUIManager.h b/ReactKit/Modules/RCTUIManager.h
index 9547e096d..3b13e69f1 100644
--- a/ReactKit/Modules/RCTUIManager.h
+++ b/ReactKit/Modules/RCTUIManager.h
@@ -12,7 +12,7 @@
@protocol RCTScrollableProtocol;
@protocol RCTViewNodeProtocol;
-@interface RCTUIManager : NSObject
+@interface RCTUIManager : NSObject
- (instancetype)initWithBridge:(RCTBridge *)bridge;
diff --git a/ReactKit/Modules/RCTUIManager.m b/ReactKit/Modules/RCTUIManager.m
index d12c8f998..75d58a65d 100644
--- a/ReactKit/Modules/RCTUIManager.m
+++ b/ReactKit/Modules/RCTUIManager.m
@@ -34,26 +34,6 @@ static void RCTTraverseViewNodes(id view, react_view_node_b
}
}
-static NSString *RCTModuleName(Class moduleClass)
-{
- if ([moduleClass respondsToSelector:@selector(moduleName)]) {
-
- return [moduleClass moduleName];
-
- } else {
-
- // Default implementation, works in most cases
- NSString *className = NSStringFromClass(moduleClass);
- if ([className hasPrefix:@"RCTUI"]) {
- className = [className substringFromIndex:@"RCT".length];
- }
- if ([className hasSuffix:@"Manager"]) {
- className = [className substringToIndex:className.length - @"Manager".length];
- }
- return className;
- }
-}
-
static NSDictionary *RCTViewModuleClasses(void)
{
static NSMutableDictionary *modules;
@@ -78,7 +58,7 @@ static NSDictionary *RCTViewModuleClasses(void)
}
// Get module name
- NSString *moduleName = RCTModuleName(cls);
+ NSString *moduleName = [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls);
// Check module name is unique
id existingClass = modules[moduleName];
@@ -131,7 +111,7 @@ static NSDictionary *RCTViewModuleClasses(void)
// Instantiate view managers
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
[RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
- viewManagers[moduleName] = [[moduleClass alloc] init];
+ viewManagers[moduleName] = [[moduleClass alloc] initWithEventDispatcher:_bridge.eventDispatcher];
}];
_viewManagers = viewManagers;
@@ -535,11 +515,6 @@ static NSDictionary *RCTViewModuleClasses(void)
}
}
-- (UIView *)viewForViewManager:(id )manager
-{
- return [manager viewWithEventDispatcher:_bridge.eventDispatcher];
-}
-
static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView, id manager)
{
// TODO: cache respondsToSelector tests
@@ -581,7 +556,9 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
if (obj == [NSNull null]) {
// Copy property from default view to current
- RCTSetProperty(shadowView, key, [defaultView valueForKey:key]);
+ // Note: not just doing `[defaultView valueForKey:key]`, the
+ // key may not exist, in which case we'd get an exception.
+ RCTCopyProperty(shadowView, defaultView, key);
} else {
RCTSetProperty(shadowView, key, obj);
}
@@ -619,10 +596,10 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
// Note the default is setup after the props are read for the first time ever
// for this className - this is ok because we only use the default for restoring
// defaults, which never happens on first creation.
- uiManager->_defaultViews[moduleName] = [uiManager viewForViewManager:manager];
+ uiManager->_defaultViews[moduleName] = [manager view];
}
- UIView *view = [uiManager viewForViewManager:manager];
+ UIView *view = [manager view];
if (view) {
// Set required properties
view.reactTag = reactTag;
@@ -1013,7 +990,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}];
}
-- (NSDictionary *)allBubblingEventTypesConfigs
++ (NSDictionary *)allBubblingEventTypesConfigs
{
NSMutableDictionary *customBubblingEventTypesConfigs = [@{
// Bubble dispatched events
@@ -1103,20 +1080,20 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
},
} mutableCopy];
- for (id viewManager in _viewManagers.allValues) {
- NSDictionary *bubblingEvents = [viewManager respondsToSelector:@selector(customBubblingEventTypes)]? [viewManager customBubblingEventTypes] : nil;
- if (bubblingEvents) {
- for (NSString *eventName in bubblingEvents) {
- RCTCAssert(!customBubblingEventTypesConfigs[eventName], @"Event %@ registered multiple times.", eventName);
+ [RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
+ if (RCTClassOverridesClassMethod(cls, @selector(customBubblingEventTypes))) {
+ NSDictionary *eventTypes = [cls customBubblingEventTypes];
+ for (NSString *eventName in eventTypes) {
+ RCTCAssert(!customBubblingEventTypesConfigs[eventName], @"Event '%@' registered multiple times.", eventName);
}
- [customBubblingEventTypesConfigs addEntriesFromDictionary:bubblingEvents];
+ [customBubblingEventTypesConfigs addEntriesFromDictionary:eventTypes];
}
- }
+ }];
return customBubblingEventTypesConfigs;
}
-- (NSDictionary *)allDirectEventTypesConfigs
++ (NSDictionary *)allDirectEventTypesConfigs
{
NSMutableDictionary *customDirectEventTypes = [@{
@"topScrollBeginDrag": @{
@@ -1154,22 +1131,21 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
},
} mutableCopy];
- for (id viewManager in _viewManagers.allValues) {
- NSDictionary *bubblingEvents = [viewManager respondsToSelector:@selector(customDirectEventTypes)] ? [viewManager customDirectEventTypes] : nil;
- if (bubblingEvents) {
- for (NSString *eventName in bubblingEvents) {
- RCTCAssert(!customDirectEventTypes[eventName], @"Event %@ registered multiple times.", eventName);
+ [RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
+ if (RCTClassOverridesClassMethod(cls, @selector(customDirectEventTypes))) {
+ NSDictionary *eventTypes = [cls customDirectEventTypes];
+ for (NSString *eventName in eventTypes) {
+ RCTCAssert(!customDirectEventTypes[eventName], @"Event '%@' registered multiple times.", eventName);
}
- [customDirectEventTypes addEntriesFromDictionary:bubblingEvents];
+ [customDirectEventTypes addEntriesFromDictionary:eventTypes];
}
- }
+ }];
return customDirectEventTypes;
}
-- (NSDictionary *)constantsToExport
++ (NSDictionary *)constantsToExport
{
- UIScreen *screen = [UIScreen mainScreen];
NSMutableDictionary *allJSConstants = [@{
@"customBubblingEventTypes": [self allBubblingEventTypesConfigs],
@"customDirectEventTypes": [self allDirectEventTypesConfigs],
@@ -1180,13 +1156,13 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
},
@"Dimensions": @{
@"window": @{
- @"width": @(screen.bounds.size.width),
- @"height": @(screen.bounds.size.height),
- @"scale": @(screen.scale),
+ @"width": @(RCTScreenSize().width),
+ @"height": @(RCTScreenSize().height),
+ @"scale": @(RCTScreenScale()),
},
@"modalFullscreenView": @{
- @"width": @(screen.bounds.size.width),
- @"height": @(screen.bounds.size.height),
+ @"width": @(RCTScreenSize().width),
+ @"height": @(RCTScreenSize().width),
},
},
@"StyleConstants": @{
@@ -1224,12 +1200,15 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
},
} mutableCopy];
- [_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, id viewManager, BOOL *stop) {
- NSDictionary *constants = [viewManager respondsToSelector:@selector(constantsToExport)] ? [viewManager constantsToExport] : nil;
- if (constants) {
- RCTAssert(allJSConstants[name] == nil , @"Cannot redefine constant namespace: %@", name);
+ [RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
+ // TODO: should these be inherited?
+ NSDictionary *constants = RCTClassOverridesClassMethod(cls, @selector(constantsToExport)) ? [cls constantsToExport] : nil;
+ if ([constants count]) {
+ NSMutableDictionary *namespace = [NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
+ RCTAssert(namespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name);
// add an additional 'Constants' namespace for each class
- allJSConstants[name] = @{@"Constants": constants};
+ namespace[@"Constants"] = constants;
+ allJSConstants[name] = [namespace copy];
}
}];
diff --git a/ReactKit/ReactKit.xcodeproj/project.pbxproj b/ReactKit/ReactKit.xcodeproj/project.pbxproj
index 5d2bbc692..036ef258a 100644
--- a/ReactKit/ReactKit.xcodeproj/project.pbxproj
+++ b/ReactKit/ReactKit.xcodeproj/project.pbxproj
@@ -50,7 +50,6 @@
83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA5F1A601EAA00E9B192 /* RCTBridge.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 */; };
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBACB1A6023D300E9B192 /* RCTConvert.m */; };
83EEC2EE1A604AB200C39218 /* RCTModuleMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 83EEC2ED1A604AB200C39218 /* RCTModuleMethod.m */; };
@@ -162,8 +161,6 @@
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 = ""; };
83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTConvert.h; sourceTree = ""; };
@@ -329,8 +326,6 @@
83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */,
83CBBA4D1A601E3B00E9B192 /* RCTLog.h */,
83CBBA4E1A601E3B00E9B192 /* RCTLog.m */,
- 83CBBA891A60204600E9B192 /* RCTModuleIDs.h */,
- 83CBBA8A1A60204600E9B192 /* RCTModuleIDs.m */,
83CBBA581A601E9000E9B192 /* RCTRedBox.h */,
83CBBA591A601E9000E9B192 /* RCTRedBox.m */,
83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */,
@@ -439,7 +434,6 @@
13E067591A70F44B002CDEE1 /* UIView+ReactKit.m in Sources */,
137029531A69923600575408 /* RCTImageDownloader.m in Sources */,
83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */,
- 83CBBA8B1A60204600E9B192 /* RCTModuleIDs.m in Sources */,
83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */,
13B080071A6947C200A75B9A /* RCTShadowRawText.m in Sources */,
13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */,
@@ -555,6 +549,7 @@
"DEBUG=1",
"$(inherited)",
);
+ GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -564,6 +559,7 @@
83CBBA411A601D0F00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
diff --git a/ReactKit/Views/RCTNavItemManager.m b/ReactKit/Views/RCTNavItemManager.m
index e509af80f..515b3437b 100644
--- a/ReactKit/Views/RCTNavItemManager.m
+++ b/ReactKit/Views/RCTNavItemManager.m
@@ -7,9 +7,9 @@
@implementation RCTNavItemManager
-- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (UIView *)view
{
- return [[RCTNavItem alloc] initWithFrame:CGRectZero];
+ return [[RCTNavItem alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(title)
diff --git a/ReactKit/Views/RCTNavigator.h b/ReactKit/Views/RCTNavigator.h
index e88b45ce9..76c6deab9 100644
--- a/ReactKit/Views/RCTNavigator.h
+++ b/ReactKit/Views/RCTNavigator.h
@@ -9,8 +9,7 @@
@property (nonatomic, strong) UIView *reactNavSuperviewLink;
@property (nonatomic, assign) NSInteger requestedTopOfStack;
-- (instancetype)initWithFrame:(CGRect)frame
- eventDispatcher:(RCTEventDispatcher *)eventDispatcher;
+- (instancetype)initWithEventDispatcher:(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 97d545948..b5769540b 100644
--- a/ReactKit/Views/RCTNavigator.m
+++ b/ReactKit/Views/RCTNavigator.m
@@ -126,7 +126,17 @@ NSInteger kNeverProgressed = -10000;
*/
@implementation RCTNavigationController
-- (instancetype)init
+- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
+{
+ RCT_NOT_DESIGNATED_INITIALIZER();
+}
+
+- (instancetype)initWithNavigationBarClass:(Class)navigationBarClass toolbarClass:(Class)toolbarClass
+{
+ RCT_NOT_DESIGNATED_INITIALIZER();
+}
+
+- (instancetype)initWithRootViewController:(UIViewController *)rootViewController
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
@@ -137,7 +147,7 @@ NSInteger kNeverProgressed = -10000;
*/
- (instancetype)initWithScrollCallback:(dispatch_block_t)callback
{
- if ((self = [super init])) {
+ if ((self = [super initWithNibName:nil bundle:nil])) {
_scrollCallback = callback;
}
return self;
@@ -265,11 +275,14 @@ NSInteger kNeverProgressed = -10000;
@implementation RCTNavigator
-- (id)initWithFrame:(CGRect)frame
- eventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (instancetype)initWithFrame:(CGRect)frame
{
- self = [super initWithFrame:frame];
- if (self) {
+ RCT_NOT_DESIGNATED_INITIALIZER();
+}
+
+- (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+{
+ if ((self = [super initWithFrame:CGRectZero])) {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(reportNavigationProgress:)];
_mostRecentProgress = kNeverProgressed;
_dummyView = [[UIView alloc] initWithFrame:CGRectZero];
@@ -311,24 +324,15 @@ NSInteger kNeverProgressed = -10000;
return;
}
_mostRecentProgress = nextProgress;
- [_eventDispatcher sendRawEventWithType:@"topNavigationProgress"
- body:@{@"fromIndex": @(_currentlyTransitioningFrom),
- @"toIndex": @(_currentlyTransitioningTo),
- @"progress": @(nextProgress),
- @"target": self.reactTag}];
+ [_eventDispatcher sendEventWithName:@"topNavigationProgress" body:@{
+ @"fromIndex": @(_currentlyTransitioningFrom),
+ @"toIndex": @(_currentlyTransitioningTo),
+ @"progress": @(nextProgress),
+ @"target": self.reactTag
+ }];
}
}
-- (instancetype)initWithFrame:(CGRect)frame
-{
- RCT_NOT_DESIGNATED_INITIALIZER();
-}
-
-- (instancetype)init
-{
- RCT_NOT_DESIGNATED_INITIALIZER();
-}
-
- (void)dealloc
{
_navigationController.delegate = nil;
@@ -438,9 +442,10 @@ NSInteger kNeverProgressed = -10000;
- (void)handleTopOfStackChanged
{
- [_eventDispatcher sendRawEventWithType:@"topNavigateBack"
- body:@{@"target":self.reactTag,
- @"stackLength":@(_navigationController.viewControllers.count)}];
+ [_eventDispatcher sendEventWithName:@"topNavigateBack" body:@{
+ @"target":self.reactTag,
+ @"stackLength":@(_navigationController.viewControllers.count)
+ }];
}
- (void)dispatchFakeScrollEvent
diff --git a/ReactKit/Views/RCTNavigatorManager.m b/ReactKit/Views/RCTNavigatorManager.m
index 317ff4865..973d4958d 100644
--- a/ReactKit/Views/RCTNavigatorManager.m
+++ b/ReactKit/Views/RCTNavigatorManager.m
@@ -8,9 +8,9 @@
@implementation RCTNavigatorManager
-- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (UIView *)view
{
- return [[RCTNavigator alloc] initWithFrame:CGRectZero eventDispatcher:eventDispatcher];
+ return [[RCTNavigator alloc] initWithEventDispatcher:self.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack)
diff --git a/ReactKit/Views/RCTNetworkImageView.m b/ReactKit/Views/RCTNetworkImageView.m
index 3f6dc8ed4..2a739cea7 100644
--- a/ReactKit/Views/RCTNetworkImageView.m
+++ b/ReactKit/Views/RCTNetworkImageView.m
@@ -4,6 +4,7 @@
#import "RCTImageDownloader.h"
#import "RCTUtils.h"
+#import "RCTConvert.h"
@implementation RCTNetworkImageView
{
@@ -52,13 +53,27 @@
self.layer.contentsScale = _defaultImage.scale;
self.layer.contents = (__bridge id)_defaultImage.CGImage;
}
- _downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() block:^(UIImage *image, NSError *error) {
- if (image) {
- self.layer.contentsScale = image.scale;
- self.layer.contents = (__bridge id)image.CGImage;
- }
- // TODO: handle errors
- }];
+ if ([imageURL.pathExtension caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
+ _downloadToken = [_imageDownloader downloadDataForURL:imageURL block:^(NSData *data, NSError *error) {
+ if (data) {
+ CAKeyframeAnimation *animation = [RCTConvert GIF:data];
+ CGImageRef firstFrame = (__bridge CGImageRef)animation.values.firstObject;
+ self.layer.bounds = CGRectMake(0, 0, CGImageGetWidth(firstFrame), CGImageGetHeight(firstFrame));
+ self.layer.contentsScale = 1.0;
+ self.layer.contentsGravity = kCAGravityResizeAspect;
+ [self.layer addAnimation:animation forKey:@"contents"];
+ }
+ // TODO: handle errors
+ }];
+ } else {
+ _downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() block:^(UIImage *image, NSError *error) {
+ if (image) {
+ self.layer.contentsScale = image.scale;
+ self.layer.contents = (__bridge id)image.CGImage;
+ }
+ // TODO: handle errors
+ }];
+ }
}
}
diff --git a/ReactKit/Views/RCTNetworkImageViewManager.m b/ReactKit/Views/RCTNetworkImageViewManager.m
index b1235d82b..5f8ad5d35 100644
--- a/ReactKit/Views/RCTNetworkImageViewManager.m
+++ b/ReactKit/Views/RCTNetworkImageViewManager.m
@@ -11,7 +11,7 @@
@implementation RCTNetworkImageViewManager
-- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (UIView *)view
{
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 1d007e88f..57ee75204 100644
--- a/ReactKit/Views/RCTRawTextManager.m
+++ b/ReactKit/Views/RCTRawTextManager.m
@@ -6,7 +6,7 @@
@implementation RCTRawTextManager
-- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (UIView *)view
{
return [[UIView alloc] init];
}
diff --git a/ReactKit/Views/RCTScrollView.h b/ReactKit/Views/RCTScrollView.h
index 9de2892e6..82667b205 100644
--- a/ReactKit/Views/RCTScrollView.h
+++ b/ReactKit/Views/RCTScrollView.h
@@ -32,6 +32,6 @@
@property (nonatomic, assign) BOOL centerContent;
@property (nonatomic, copy) NSArray *stickyHeaderIndices;
-- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTEventDispatcher *)eventDispatcher;
+- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
@end
diff --git a/ReactKit/Views/RCTScrollView.m b/ReactKit/Views/RCTScrollView.m
index 0af80319b..157cd406d 100644
--- a/ReactKit/Views/RCTScrollView.m
+++ b/ReactKit/Views/RCTScrollView.m
@@ -8,6 +8,7 @@
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTUIManager.h"
+#import "RCTUtils.h"
#import "UIView+ReactKit.h"
CGFloat const ZINDEX_DEFAULT = 0;
@@ -122,6 +123,7 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
*/
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
+ //TODO: shouldn't this call super if _shouldDisableScrollInteraction returns NO?
return ![self _shouldDisableScrollInteraction];
}
@@ -260,9 +262,14 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
@synthesize nativeMainScrollDelegate = _nativeMainScrollDelegate;
-- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (instancetype)initWithFrame:(CGRect)frame
{
- if ((self = [super initWithFrame:frame])) {
+ RCT_NOT_DESIGNATED_INITIALIZER();
+}
+
+- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+{
+ if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
_scrollView = [[RCTCustomScrollView alloc] initWithFrame:CGRectZero];
diff --git a/ReactKit/Views/RCTScrollViewManager.m b/ReactKit/Views/RCTScrollViewManager.m
index 1db32aae6..5100d1186 100644
--- a/ReactKit/Views/RCTScrollViewManager.m
+++ b/ReactKit/Views/RCTScrollViewManager.m
@@ -7,9 +7,9 @@
@implementation RCTScrollViewManager
-- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (UIView *)view
{
- return [[RCTScrollView alloc] initWithFrame:CGRectZero eventDispatcher:eventDispatcher];
+ return [[RCTScrollView alloc] initWithEventDispatcher:self.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(alwaysBounceHorizontal)
@@ -36,7 +36,7 @@ RCT_EXPORT_VIEW_PROPERTY(contentInset);
RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets);
RCT_EXPORT_VIEW_PROPERTY(contentOffset);
-- (NSDictionary *)constantsToExport
++ (NSDictionary *)constantsToExport
{
return
@{
diff --git a/ReactKit/Views/RCTStaticImageManager.m b/ReactKit/Views/RCTStaticImageManager.m
index 86eac9a12..f4f86abb5 100644
--- a/ReactKit/Views/RCTStaticImageManager.m
+++ b/ReactKit/Views/RCTStaticImageManager.m
@@ -9,14 +9,26 @@
@implementation RCTStaticImageManager
-- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (UIView *)view
{
return [[RCTStaticImage alloc] init];
}
-RCT_REMAP_VIEW_PROPERTY(src, image)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode)
+- (void)set_src:(id)json forView:(RCTStaticImage *)view withDefaultView:(RCTStaticImage *)defaultView
+{
+ if (json) {
+ if ([json isKindOfClass:[NSString class]] && [[json pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
+ [view.layer addAnimation:[RCTConvert GIF:json] forKey:@"contents"];
+ } else {
+ view.image = [RCTConvert UIImage:json];
+ }
+ } else {
+ view.image = defaultView.image;
+ }
+}
+
- (void)set_capInsets:(id)json forView:(RCTStaticImage *)view withDefaultView:(RCTStaticImage *)defaultView
{
view.capInsets = json ? [RCTConvert UIEdgeInsets:json] : defaultView.capInsets;
diff --git a/ReactKit/Views/RCTTextField.h b/ReactKit/Views/RCTTextField.h
index 45bacd8ac..2a0225f27 100644
--- a/ReactKit/Views/RCTTextField.h
+++ b/ReactKit/Views/RCTTextField.h
@@ -10,6 +10,6 @@
@property (nonatomic, assign) BOOL autoCorrect;
@property (nonatomic, assign) UIEdgeInsets paddingEdgeInsets; // TODO: contentInset
-- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTEventDispatcher *)eventDispatcher;
+- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
@end
diff --git a/ReactKit/Views/RCTTextField.m b/ReactKit/Views/RCTTextField.m
index 50f8170e7..4a70e0460 100644
--- a/ReactKit/Views/RCTTextField.m
+++ b/ReactKit/Views/RCTTextField.m
@@ -14,14 +14,14 @@
BOOL _jsRequestingFirstResponder;
}
-- (instancetype)init
+- (instancetype)initWithFrame:(CGRect)frame
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
-- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
- if ((self = [super initWithFrame:frame])) {
+ if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
[self addTarget:self action:@selector(_textFieldDidChange) forControlEvents:UIControlEventEditingChanged];
diff --git a/ReactKit/Views/RCTTextFieldManager.m b/ReactKit/Views/RCTTextFieldManager.m
index cb037ec2e..339a7803e 100644
--- a/ReactKit/Views/RCTTextFieldManager.m
+++ b/ReactKit/Views/RCTTextFieldManager.m
@@ -8,9 +8,9 @@
@implementation RCTTextFieldManager
-- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (UIView *)view
{
- return [[RCTTextField alloc] initWithFrame:CGRectZero eventDispatcher:eventDispatcher];
+ return [[RCTTextField alloc] initWithEventDispatcher:self.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(caretHidden)
diff --git a/ReactKit/Views/RCTTextManager.m b/ReactKit/Views/RCTTextManager.m
index 8a2f5bba6..f7f096dba 100644
--- a/ReactKit/Views/RCTTextManager.m
+++ b/ReactKit/Views/RCTTextManager.m
@@ -13,7 +13,7 @@
@implementation RCTTextManager
-- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (UIView *)view
{
UILabel *label = [[UILabel alloc] init];
label.numberOfLines = 0;
diff --git a/ReactKit/Views/RCTUIActivityIndicatorViewManager.m b/ReactKit/Views/RCTUIActivityIndicatorViewManager.m
index 7cc352e35..b7e0971e7 100644
--- a/ReactKit/Views/RCTUIActivityIndicatorViewManager.m
+++ b/ReactKit/Views/RCTUIActivityIndicatorViewManager.m
@@ -6,9 +6,9 @@
@implementation RCTUIActivityIndicatorViewManager
-- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (UIView *)view
{
- return [[UIActivityIndicatorView alloc] initWithFrame:CGRectZero];
+ return [[UIActivityIndicatorView alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(activityIndicatorViewStyle)
@@ -28,7 +28,7 @@ RCT_EXPORT_VIEW_PROPERTY(color)
}
}
-- (NSDictionary *)constantsToExport
++ (NSDictionary *)constantsToExport
{
return
@{
diff --git a/ReactKit/Views/RCTUIViewManager.h b/ReactKit/Views/RCTUIViewManager.h
index 92d7e1910..acec4c78a 100644
--- a/ReactKit/Views/RCTUIViewManager.h
+++ b/ReactKit/Views/RCTUIViewManager.h
@@ -4,6 +4,12 @@
#import "RCTExport.h"
+@class RCTEventDispatcher;
+
@interface RCTUIViewManager : NSObject
+- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
+
+@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
+
@end
diff --git a/ReactKit/Views/RCTUIViewManager.m b/ReactKit/Views/RCTUIViewManager.m
index dbdcb5473..b2dc053b3 100644
--- a/ReactKit/Views/RCTUIViewManager.m
+++ b/ReactKit/Views/RCTUIViewManager.m
@@ -3,13 +3,38 @@
#import "RCTUIViewManager.h"
#import "RCTConvert.h"
+#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTShadowView.h"
#import "RCTView.h"
@implementation RCTUIViewManager
+{
+ __weak RCTEventDispatcher *_eventDispatcher;
+}
-- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+{
+ if ((self = [super init])) {
+ _eventDispatcher = eventDispatcher;
+ }
+ return self;
+}
+
++ (NSString *)moduleName
+{
+ // Default implementation, works in most cases
+ NSString *name = NSStringFromClass(self);
+ if ([name hasPrefix:@"RCTUI"]) {
+ name = [name substringFromIndex:@"RCT".length];
+ }
+ if ([name hasSuffix:@"Manager"]) {
+ name = [name substringToIndex:name.length - @"Manager".length];
+ }
+ return name;
+}
+
+- (UIView *)view
{
return [[UIView alloc] init];
}
@@ -29,7 +54,7 @@ RCT_REMAP_VIEW_PROPERTY(shadowRadius, layer.shadowRadius)
RCT_REMAP_VIEW_PROPERTY(borderColor, layer.borderColor);
RCT_REMAP_VIEW_PROPERTY(borderRadius, layer.cornerRadius)
RCT_REMAP_VIEW_PROPERTY(borderWidth, layer.borderWidth)
-RCT_REMAP_VIEW_PROPERTY(transformMatrix, view.layer.transform)
+RCT_REMAP_VIEW_PROPERTY(transformMatrix, layer.transform)
- (void)set_overflow:(id)json
forView:(UIView *)view
diff --git a/ReactKit/Views/RCTViewManager.m b/ReactKit/Views/RCTViewManager.m
index 5acfa7332..7add6d617 100644
--- a/ReactKit/Views/RCTViewManager.m
+++ b/ReactKit/Views/RCTViewManager.m
@@ -6,12 +6,11 @@
@implementation RCTViewManager
-- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+- (UIView *)view
{
return [[RCTView alloc] init];
}
-RCT_EXPORT_VIEW_PROPERTY(accessibilityLabel)
RCT_EXPORT_VIEW_PROPERTY(pointerEvents)
@end
diff --git a/ReactKit/Views/RCTWrapperViewController.m b/ReactKit/Views/RCTWrapperViewController.m
index b638d2b50..b5425fbcd 100644
--- a/ReactKit/Views/RCTWrapperViewController.m
+++ b/ReactKit/Views/RCTWrapperViewController.m
@@ -96,7 +96,7 @@
- (void)rightButtonTapped
{
- [_eventDispatcher sendRawEventWithType:@"topNavRightButtonTap" body:@{@"target":_navItem.reactTag}];
+ [_eventDispatcher sendEventWithName:@"topNavRightButtonTap" body:@{@"target":_navItem.reactTag}];
}
- (void)didMoveToParentViewController:(UIViewController *)parent