2015-02-03 updates

- Add back providesModule transform to JSAppServer | Joseph Savona
- [ReactKit] fix open source performance issue | John Harper
- [ReactKit] improve ReactIOSEventEmitter logics | Andrew Rasmussen
- [reactkit] fix web view JS executor and bind it to Command-d | John Harper
- Removed hardcoded RCTModuleIDs | Nick Lockwood
- [ReactKit] Animated GIF support | Alex Akers
- [ReactKit] Update RCTBridge to support non-`id` argument types | Alex Akers
- [reactkit] fix typo in RCTCopyProperty() change | John Harper
- [reactkit] fix shadow view crash on missing properties | John Harper
- [reactkit] fix transform keypath | John Harper
This commit is contained in:
Christopher Chedeau 2015-02-03 16:15:20 -08:00
parent ccd8f184af
commit 6153fffb30
46 changed files with 719 additions and 506 deletions

View File

@ -9,6 +9,7 @@
var React = require('react-native'); var React = require('react-native');
var { var {
Bundler, Bundler,
Image,
StyleSheet, StyleSheet,
Text, Text,
TouchableHighlight, 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() { render() {
return ( return (
<TouchableHighlight onPress={this.props.onPress} underlayColor={'clear'} activeOpacity={0.5}> <TouchableHighlight onPress={this.props.onPress} underlayColor={'clear'} activeOpacity={0.5}>
<View style={[styles.cell, this.cellStyle()]}> <View style={[styles.cell, this.cellStyle()]}>
<Text style={[styles.cellText, this.textStyle()]}> <Image source={{uri: this.imageContents()}} />
{this.textContents()}
</Text>
</View> </View>
</TouchableHighlight> </TouchableHighlight>
); );

View File

@ -169,12 +169,20 @@ var ReactIOSEventEmitter = merge(ReactEventEmitterMixin, {
var target = nativeEvent.target; var target = nativeEvent.target;
if (target !== null && target !== undefined) { if (target !== null && target !== undefined) {
if (target < ReactIOSTagHandles.tagsStartAt) { 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__) { if (__DEV__) {
if (jj === 0) {
warning( warning(
false, false,
'A view is reporting that a touch occured on tag zero.' 'A view is reporting that a touch occured on tag zero.'
); );
} }
}
continue;
} else { } else {
rootNodeID = NodeHandle.getRootNodeID(target); rootNodeID = NodeHandle.getRootNodeID(target);
} }

View File

@ -37,12 +37,14 @@ function createStrictShapeTypeChecker(shapeTypes) {
var allKeys = merge(props[propName], shapeTypes); var allKeys = merge(props[propName], shapeTypes);
for (var key in allKeys) { for (var key in allKeys) {
var checker = shapeTypes[key]; var checker = shapeTypes[key];
if (!checker) {
invariant( invariant(
checker, false,
`Invalid props.${propName} key \`${key}\` supplied to \`${componentName}\`.` + `Invalid props.${propName} key \`${key}\` supplied to \`${componentName}\`.` +
`\nBad object: ` + JSON.stringify(props[propName], null, ' ') + `\nBad object: ` + JSON.stringify(props[propName], null, ' ') +
`\nValid keys: ` + JSON.stringify(Object.keys(shapeTypes), null, ' ') `\nValid keys: ` + JSON.stringify(Object.keys(shapeTypes), null, ' ')
); );
}
var error = checker(propValue, key, componentName, location); var error = checker(propValue, key, componentName, location);
if (error) { if (error) {
invariant( invariant(

View File

@ -33,10 +33,9 @@ static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
*/ */
@interface RCTBridge : NSObject <RCTInvalidating> @interface RCTBridge : NSObject <RCTInvalidating>
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor - (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor;
javaScriptModulesConfig:(NSDictionary *)javaScriptModulesConfig;
- (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; - (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete;
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher; @property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;

View File

@ -8,7 +8,6 @@
#import "RCTInvalidating.h" #import "RCTInvalidating.h"
#import "RCTEventDispatcher.h" #import "RCTEventDispatcher.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTModuleIDs.h"
#import "RCTUtils.h" #import "RCTUtils.h"
/** /**
@ -36,19 +35,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldFlushDateMillis 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 NSDictionary *RCTNativeModuleClasses(void)
{ {
static NSMutableDictionary *modules; static NSMutableDictionary *modules;
@ -73,7 +59,7 @@ static NSDictionary *RCTNativeModuleClasses(void)
} }
// Get module name // Get module name
NSString *moduleName = RCTModuleName(cls); NSString *moduleName = [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls);
// Check module name is unique // Check module name is unique
id existingClass = modules[moduleName]; id existingClass = modules[moduleName];
@ -90,23 +76,22 @@ static NSDictionary *RCTNativeModuleClasses(void)
@implementation RCTBridge @implementation RCTBridge
{ {
NSMutableDictionary *_moduleInstances; NSMutableDictionary *_moduleInstances;
NSDictionary *_javaScriptModulesConfig; NSMutableDictionary *_moduleIDLookup;
NSMutableDictionary *_methodIDLookup;
id<RCTJavaScriptExecutor> _javaScriptExecutor; id<RCTJavaScriptExecutor> _javaScriptExecutor;
} }
static id<RCTJavaScriptExecutor> _latestJSExecutor; static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor - (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
javaScriptModulesConfig:(NSDictionary *)javaScriptModulesConfig
{ {
if ((self = [super init])) { if ((self = [super init])) {
_javaScriptExecutor = javaScriptExecutor; _javaScriptExecutor = javaScriptExecutor;
_latestJSExecutor = _javaScriptExecutor; _latestJSExecutor = _javaScriptExecutor;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; _eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_javaScriptModulesConfig = javaScriptModulesConfig;
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL); _shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);
// Register modules // Instantiate modules
_moduleInstances = [[NSMutableDictionary alloc] init]; _moduleInstances = [[NSMutableDictionary alloc] init];
[RCTNativeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) { [RCTNativeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
if (_moduleInstances[moduleName] == nil) { if (_moduleInstances[moduleName] == nil) {
@ -118,6 +103,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
} }
}]; }];
_moduleIDLookup = [[NSMutableDictionary alloc] init];
_methodIDLookup = [[NSMutableDictionary alloc] init];
[self doneRegisteringModules]; [self doneRegisteringModules];
} }
@ -168,10 +155,18 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
/** /**
* Like JS::call, for objective-c. * Like JS::call, for objective-c.
*/ */
- (void)enqueueJSCall:(NSUInteger)moduleID methodID:(NSUInteger)methodID args:(NSArray *)args - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
{ {
RCTAssertMainThread(); NSNumber *moduleID = _moduleIDLookup[moduleDotMethod];
[self _invokeRemoteJSModule:moduleID methodID:methodID args:args]; 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 - (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
@ -217,13 +212,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
callback:processResponse]; 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. * TODO (#5906496): Have responses piggy backed on a round trip with ObjC->JS requests.
*/ */
@ -334,19 +322,19 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return; return;
} }
NSInvocation *invocation = [RCTBridge invocationForAdditionalArguments:methodArity];
// TODO: we should just store module instances by index, since that's how we look them up anyway // TODO: we should just store module instances by index, since that's how we look them up anyway
id target = strongSelf->_moduleInstances[moduleName]; id target = strongSelf->_moduleInstances[moduleName];
RCTAssert(target != nil, @"No module found for name '%@'", moduleName); RCTAssert(target != nil, @"No module found for name '%@'", moduleName);
[invocation setArgument:&target atIndex:0];
SEL selector = method.selector; SEL selector = method.selector;
NSMethodSignature *methodSignature = [target methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setArgument:&target atIndex:0];
[invocation setArgument:&selector atIndex:1]; [invocation setArgument:&selector atIndex:1];
// Retain used blocks until after invocation completes. // 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) { [params enumerateObjectsUsingBlock:^(id param, NSUInteger idx, BOOL *stop) {
if ([param isEqual:[NSNull null]]) { if ([param isEqual:[NSNull null]]) {
@ -357,7 +345,57 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
param = block; param = block;
} }
[invocation setArgument:&param 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:&param atIndex:argIdx];
}
}]; }];
@try { @try {
@ -366,10 +404,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
@catch (NSException *exception) { @catch (NSException *exception) {
RCTLogMustFix(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, target, params, 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; return YES;
@ -412,16 +446,71 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return invocation; 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 - (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(); RCTAssertMainThread();
RCTAssert(_javaScriptModulesConfig != nil, @"JS module config not loaded in APP");
NSMutableDictionary *objectsToInject = [NSMutableDictionary dictionary]; /**
* 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...
*/
// Dictionary of { moduleName0: { moduleID: 0, methods: { methodName0: { methodID: 0, type: remote }, methodName1: { ... }, ... }, ... }
NSUInteger moduleCount = RCTExportedMethodsByModule().count; NSUInteger moduleCount = RCTExportedMethodsByModule().count;
NSMutableDictionary *moduleConfigs = [NSMutableDictionary dictionaryWithCapacity:RCTExportedMethodsByModule().count]; NSMutableDictionary *remoteModules = [NSMutableDictionary dictionaryWithCapacity:RCTExportedMethodsByModule().count];
for (NSUInteger i = 0; i < moduleCount; i++) { for (NSUInteger i = 0; i < moduleCount; i++) {
NSString *moduleName = RCTExportedModuleNameAtSortedIndex(i); NSString *moduleName = RCTExportedModuleNameAtSortedIndex(i);
NSArray *rawMethods = RCTExportedMethodsByModule()[moduleName]; NSArray *rawMethods = RCTExportedMethodsByModule()[moduleName];
@ -433,37 +522,89 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}; };
}]; }];
NSMutableDictionary *moduleConfig = [NSMutableDictionary dictionary]; NSDictionary *module = @{
moduleConfig[@"moduleID"] = @(i); @"moduleID": @(i),
moduleConfig[@"methods"] = methods; @"methods": methods
id target = [_moduleInstances objectForKey:moduleName];
if ([target respondsToSelector:@selector(constantsToExport)]) {
moduleConfig[@"constants"] = [target constantsToExport];
}
moduleConfigs[moduleName] = moduleConfig;
}
NSDictionary *batchedBridgeConfig = @{
@"remoteModuleConfig": moduleConfigs,
@"localModulesConfig": _javaScriptModulesConfig
}; };
NSString *configJSON = RCTJSONStringify(batchedBridgeConfig, NULL); id target = _moduleInstances[moduleName];
objectsToInject[@"__fbBatchedBridgeConfig"] = configJSON; if (RCTClassOverridesClassMethod([target class], @selector(constantsToExport))) {
module = [module mutableCopy];
((NSMutableDictionary *)module)[@"constants"] = [[target class] constantsToExport];
}
remoteModules[moduleName] = module;
}
/**
* 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); dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[objectsToInject enumerateKeysAndObjectsUsingBlock:^(NSString *objectName, NSString *script, BOOL *stop) { [_javaScriptExecutor injectJSONText:configJSON asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:^(id err) {
[_javaScriptExecutor injectJSONText:script asGlobalObjectNamed:objectName callback:^(id err) {
dispatch_semaphore_signal(semaphore); 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) { if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) {
RCTLogMustFix(@"JavaScriptExecutor take too long to inject JSON object"); RCTLogMustFix(@"JavaScriptExecutor take too long to inject JSON object");
} }
} }
}
- (void)registerRootView:(RCTRootView *)rootView - (void)registerRootView:(RCTRootView *)rootView
{ {

View File

@ -41,6 +41,7 @@
+ (UIColor *)UIColor:(id)json; + (UIColor *)UIColor:(id)json;
+ (CGColorRef)CGColor:(id)json; + (CGColorRef)CGColor:(id)json;
+ (CAKeyframeAnimation *)GIF:(id)json;
+ (UIImage *)UIImage:(id)json; + (UIImage *)UIImage:(id)json;
+ (CGImageRef)CGImage:(id)json; + (CGImageRef)CGImage:(id)json;

View File

@ -2,6 +2,9 @@
#import "RCTConvert.h" #import "RCTConvert.h"
#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import "RCTLog.h" #import "RCTLog.h"
CGFloat const RCTDefaultFontSize = 14; CGFloat const RCTDefaultFontSize = 14;
@ -426,6 +429,83 @@ RCT_STRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty"]
return [self UIColor:json].CGColor; 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 + (UIImage *)UIImage:(id)json
{ {
if (![json isKindOfClass:[NSString class]]) { if (![json isKindOfClass:[NSString class]]) {

View File

@ -33,9 +33,11 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
- (instancetype)initWithBridge:(RCTBridge *)bridge; - (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 * Send an array of touch events

View File

@ -3,7 +3,6 @@
#import "RCTEventDispatcher.h" #import "RCTEventDispatcher.h"
#import "RCTBridge.h" #import "RCTBridge.h"
#import "RCTModuleIDs.h"
#import "UIView+ReactKit.h" #import "UIView+ReactKit.h"
@implementation RCTEventDispatcher @implementation RCTEventDispatcher
@ -19,39 +18,13 @@
return self; 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]], 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 [_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent"
methodID:RCTEventEmitterReceiveEvent args:@[body[@"target"], name, body]];
args:@[body[@"target"], eventType, body]];
} }
/** /**
@ -69,30 +42,32 @@
touches:(NSArray *)touches touches:(NSArray *)touches
changedIndexes:(NSArray *)changedIndexes changedIndexes:(NSArray *)changedIndexes
{ {
static NSString *events[] = {
@"topTouchStart",
@"topTouchMove",
@"topTouchEnd",
@"topTouchCancel",
};
RCTAssert(touches.count, @"No touches in touchEventArgsForOrderedTouches"); RCTAssert(touches.count, @"No touches in touchEventArgsForOrderedTouches");
[_bridge enqueueJSCall:RCTModuleIDReactIOSEventEmitter [_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
methodID:RCTEventEmitterReceiveTouches args:@[events[type], touches, changedIndexes]];
args:@[[self touchEvents][type], touches, changedIndexes]];
} }
- (void)sendTextEventWithType:(RCTTextEventType)type - (void)sendTextEventWithType:(RCTTextEventType)type
reactTag:(NSNumber *)reactTag reactTag:(NSNumber *)reactTag
text:(NSString *)text text:(NSString *)text
{ {
static NSArray *events; static NSString *events[] = {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
events = @[
@"topFocus", @"topFocus",
@"topBlur", @"topBlur",
@"topChange", @"topChange",
@"topSubmitEditing", @"topSubmitEditing",
@"topEndEditing", @"topEndEditing",
]; };
});
[self sendRawEventWithType:events[type] body:@{ [self sendEventWithName:events[type] body:@{
@"text": text, @"text": text,
@"target": reactTag @"target": reactTag
}]; }];
@ -111,18 +86,14 @@
scrollView:(UIScrollView *)scrollView scrollView:(UIScrollView *)scrollView
userData:(NSDictionary *)userData userData:(NSDictionary *)userData
{ {
static NSArray *events; static NSString *events[] = {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
events = @[
@"topScrollBeginDrag", @"topScrollBeginDrag",
@"topScroll", @"topScroll",
@"topScrollEndDrag", @"topScrollEndDrag",
@"topMomentumScrollBegin", @"topMomentumScrollBegin",
@"topMomentumScrollEnd", @"topMomentumScrollEnd",
@"topScrollAnimationEnd", @"topScrollAnimationEnd",
]; };
});
NSDictionary *body = @{ NSDictionary *body = @{
@"contentOffset": @{ @"contentOffset": @{
@ -147,7 +118,7 @@
body = mutableBody; body = mutableBody;
} }
[self sendRawEventWithType:events[type] body:body]; [self sendEventWithName:events[type] body:body];
} }
@end @end

View File

@ -26,6 +26,7 @@ extern NSString *RCTExportedModuleNameAtSortedIndex(NSUInteger index);
extern NSDictionary *RCTExportedMethodsByModule(void); extern NSDictionary *RCTExportedMethodsByModule(void);
extern BOOL RCTSetProperty(id target, NSString *keypath, id value); 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); 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 * 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. * 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 <NSObject> @protocol RCTNativeViewModule <NSObject>
/** /**
* 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 @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, * 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; - (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 * 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" * @"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 * 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 * To deprecate, hopefully

View File

@ -349,6 +349,36 @@ BOOL RCTSetProperty(id target, NSString *keypath, id value)
return YES; 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) BOOL RCTCallSetter(id target, SEL setter, id value)
{ {
// Get property name // Get property name

View File

@ -2,12 +2,16 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
typedef void (^RCTDataDownloadBlock)(NSData *data, NSError *error);
typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error); typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
@interface RCTImageDownloader : NSObject @interface RCTImageDownloader : NSObject
+ (instancetype)sharedInstance; + (instancetype)sharedInstance;
- (id)downloadDataForURL:(NSURL *)url
block:(RCTDataDownloadBlock)block;
- (id)downloadImageForURL:(NSURL *)url - (id)downloadImageForURL:(NSURL *)url
size:(CGSize)size size:(CGSize)size
scale:(CGFloat)scale scale:(CGFloat)scale

View File

@ -18,6 +18,20 @@
return sharedInstance; 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 - (id)downloadImageForURL:(NSURL *)url
size:(CGSize)size size:(CGSize)size
scale:(CGFloat)scale scale:(CGFloat)scale

View File

@ -1,60 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <Foundation/Foundation.h>
/**
* 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

View File

@ -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

View File

@ -6,11 +6,11 @@
#import "RCTContextExecutor.h" #import "RCTContextExecutor.h"
#import "RCTEventDispatcher.h" #import "RCTEventDispatcher.h"
#import "RCTJavaScriptAppEngine.h" #import "RCTJavaScriptAppEngine.h"
#import "RCTModuleIDs.h"
#import "RCTTouchHandler.h" #import "RCTTouchHandler.h"
#import "RCTUIManager.h" #import "RCTUIManager.h"
#import "RCTUtils.h" #import "RCTUtils.h"
#import "RCTViewManager.h" #import "RCTViewManager.h"
#import "RCTWebViewExecutor.h"
#import "UIView+ReactKit.h" #import "UIView+ReactKit.h"
#import "RCTKeyCommands.h" #import "RCTKeyCommands.h"
@ -23,6 +23,8 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
RCTTouchHandler *_touchHandler; RCTTouchHandler *_touchHandler;
} }
static BOOL _useWebExec;
+ (void)initialize + (void)initialize
{ {
@ -34,6 +36,13 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
action:^(UIKeyCommand *command) { action:^(UIKeyCommand *command) {
[self reloadAll]; [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 #endif
@ -98,8 +107,8 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
@"rootTag": self.reactTag ?: @0, @"rootTag": self.reactTag ?: @0,
@"initialProps": self.initialProperties ?: @{}, @"initialProps": self.initialProperties ?: @{},
}; };
[_bridge enqueueJSCall:RCTModuleIDBundler
methodID:RCTBundlerRunApplication [_bridge enqueueJSCall:@"Bundler.runApplication"
args:@[moduleName, appParameters]]; args:@[moduleName, appParameters]];
} }
} }
@ -123,9 +132,13 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
[_executor invalidate]; [_executor invalidate];
[_bridge invalidate]; [_bridge invalidate];
if (!_useWebExec) {
_executor = [[RCTContextExecutor alloc] init]; _executor = [[RCTContextExecutor alloc] init];
_bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor } else {
javaScriptModulesConfig:[RCTModuleIDs config]]; _executor = [[RCTWebViewExecutor alloc] init];
}
_bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor];
_appEngine = [[RCTJavaScriptAppEngine alloc] initWithBridge:_bridge]; _appEngine = [[RCTJavaScriptAppEngine alloc] initWithBridge:_bridge];
_touchHandler = [[RCTTouchHandler alloc] initWithEventDispatcher:_bridge.eventDispatcher rootView:self]; _touchHandler = [[RCTTouchHandler alloc] initWithEventDispatcher:_bridge.eventDispatcher rootView:self];
@ -165,22 +178,4 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
[[NSNotificationCenter defaultCenter] postNotificationName:RCTRootViewReloadNotification object:nil]; [[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 @end

View File

@ -4,9 +4,8 @@
@interface RCTSparseArray : NSObject <NSCopying> @interface RCTSparseArray : NSObject <NSCopying>
- (instancetype)init; - (instancetype)initWithCapacity:(NSUInteger)capacity NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithCapacity:(NSUInteger)capacity; - (instancetype)initWithSparseArray:(RCTSparseArray *)sparseArray NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithSparseArray:(RCTSparseArray *)sparseArray;
+ (instancetype)sparseArray; + (instancetype)sparseArray;
+ (instancetype)sparseArrayWithCapacity:(NSUInteger)capacity; + (instancetype)sparseArrayWithCapacity:(NSUInteger)capacity;

View File

@ -28,14 +28,6 @@
return self; return self;
} }
- (instancetype)initWithStorage:(NSDictionary *)storage
{
if ((self = [super init])) {
_storage = [storage copy];
}
return self;
}
+ (instancetype)sparseArray + (instancetype)sparseArray
{ {
return [[self alloc] init]; return [[self alloc] init];

View File

@ -11,6 +11,9 @@
#import "RCTUtils.h" #import "RCTUtils.h"
#import "UIView+ReactKit.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 @implementation RCTTouchHandler
{ {
__weak UIView *_rootView; __weak UIView *_rootView;
@ -27,11 +30,6 @@
NSMutableArray *_touchViews; NSMutableArray *_touchViews;
} }
- (instancetype)init
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (instancetype)initWithTarget:(id)target action:(SEL)action - (instancetype)initWithTarget:(id)target action:(SEL)action
{ {
RCT_NOT_DESIGNATED_INITIALIZER(); RCT_NOT_DESIGNATED_INITIALIZER();

View File

@ -20,8 +20,9 @@ id RCTJSONParse(NSString *jsonString, NSError **error);
// Get MD5 hash of a string // Get MD5 hash of a string
NSString *RCTMD5Hash(NSString *string); NSString *RCTMD5Hash(NSString *string);
// Get screen scale in a thread-safe way // Get screen metrics in a thread-safe way
CGFloat RCTScreenScale(void); CGFloat RCTScreenScale(void);
CGSize RCTScreenSize(void);
// Round float coordinates to nearest whole screen pixel (not point) // Round float coordinates to nearest whole screen pixel (not point)
CGFloat RCTRoundPixelValue(CGFloat value); CGFloat RCTRoundPixelValue(CGFloat value);
@ -34,3 +35,7 @@ NSTimeInterval RCTTGetAbsoluteTime(void);
// Method swizzling // Method swizzling
void RCTSwapClassMethods(Class cls, SEL original, SEL replacement); void RCTSwapClassMethods(Class cls, SEL original, SEL replacement);
void RCTSwapInstanceMethods(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);

View File

@ -35,7 +35,7 @@ NSString *RCTMD5Hash(NSString *string)
CGFloat RCTScreenScale() CGFloat RCTScreenScale()
{ {
static CGFloat scale = -1; static CGFloat scale;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
if (![NSThread isMainThread]) { if (![NSThread isMainThread]) {
@ -50,6 +50,23 @@ CGFloat RCTScreenScale()
return scale; 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 RCTRoundPixelValue(CGFloat value)
{ {
CGFloat scale = RCTScreenScale(); CGFloat scale = RCTScreenScale();
@ -120,3 +137,24 @@ void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement)
method_exchangeImplementations(originalMethod, replacementMethod); method_exchangeImplementations(originalMethod, replacementMethod);
} }
} }
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;
}

View File

@ -41,6 +41,11 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
return self; return self;
} }
- (id)init
{
return [self initWithWebView:[[UIWebView alloc] init]];
}
- (BOOL)isValid - (BOOL)isValid
{ {
return _webView != nil; return _webView != nil;
@ -98,7 +103,13 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
sourceURL:(NSURL *)url sourceURL:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete onComplete:(RCTJavaScriptCompleteBlock)onComplete
{ {
RCTAssertMainThread(); if (![NSThread isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self executeApplicationScript:script sourceURL:url onComplete:onComplete];
});
return;
}
RCTAssert(onComplete != nil, @""); RCTAssert(onComplete != nil, @"");
_onApplicationScriptLoaded = onComplete; _onApplicationScriptLoaded = onComplete;
@ -137,26 +148,12 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
*/ */
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
{ {
if (![NSThread isMainThread]) { dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC);
[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_after(when, dispatch_get_main_queue(), ^{
* 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(); RCTAssertMainThread();
block(); block();
});
} }
/** /**

View File

@ -4,7 +4,6 @@
#import "RCTBridge.h" #import "RCTBridge.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTModuleIDs.h"
#import "RCTSparseArray.h" #import "RCTSparseArray.h"
#import "RCTUtils.h" #import "RCTUtils.h"
@ -55,6 +54,11 @@
id _updateTimer; id _updateTimer;
} }
+ (NSArray *)JSMethods
{
return @[@"RCTJSTimers.callTimers"];
}
- (instancetype)initWithBridge:(RCTBridge *)bridge - (instancetype)initWithBridge:(RCTBridge *)bridge
{ {
if ((self = [super init])) { if ((self = [super init])) {
@ -143,7 +147,7 @@
// call timers that need to be called // call timers that need to be called
if ([timersToCall count] > 0) { 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. * Date.now() from JS and then subtracting that from the current time here.
*/ */
- (void)createTimer:(NSNumber *)callbackID - (void)createTimer:(NSNumber *)callbackID
duration:(NSNumber *)jsDuration duration:(double)jsDuration
jsSchedulingTime:(NSNumber *)jsSchedulingTime jsSchedulingTime:(double)jsSchedulingTime
repeats:(NSNumber *)repeats repeats:(BOOL)repeats
{ {
RCT_EXPORT(); RCT_EXPORT();
NSTimeInterval interval = jsDuration.doubleValue / 1000; NSTimeInterval interval = jsDuration / 1000;
NSTimeInterval jsCreationTimeSinceUnixEpoch = jsSchedulingTime.doubleValue / 1000; NSTimeInterval jsCreationTimeSinceUnixEpoch = jsSchedulingTime / 1000;
NSTimeInterval currentTimeSinceUnixEpoch = [[NSDate date] timeIntervalSince1970]; NSTimeInterval currentTimeSinceUnixEpoch = [[NSDate date] timeIntervalSince1970];
NSTimeInterval jsSchedulingOverhead = currentTimeSinceUnixEpoch - jsCreationTimeSinceUnixEpoch; NSTimeInterval jsSchedulingOverhead = currentTimeSinceUnixEpoch - jsCreationTimeSinceUnixEpoch;
if (jsSchedulingOverhead < 0) { if (jsSchedulingOverhead < 0) {

View File

@ -12,7 +12,7 @@
@protocol RCTScrollableProtocol; @protocol RCTScrollableProtocol;
@protocol RCTViewNodeProtocol; @protocol RCTViewNodeProtocol;
@interface RCTUIManager : NSObject <RCTInvalidating, RCTNativeModule> @interface RCTUIManager : NSObject <RCTNativeModule, RCTInvalidating>
- (instancetype)initWithBridge:(RCTBridge *)bridge; - (instancetype)initWithBridge:(RCTBridge *)bridge;

View File

@ -34,26 +34,6 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> 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 NSDictionary *RCTViewModuleClasses(void)
{ {
static NSMutableDictionary *modules; static NSMutableDictionary *modules;
@ -78,7 +58,7 @@ static NSDictionary *RCTViewModuleClasses(void)
} }
// Get module name // Get module name
NSString *moduleName = RCTModuleName(cls); NSString *moduleName = [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls);
// Check module name is unique // Check module name is unique
id existingClass = modules[moduleName]; id existingClass = modules[moduleName];
@ -131,7 +111,7 @@ static NSDictionary *RCTViewModuleClasses(void)
// Instantiate view managers // Instantiate view managers
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init]; NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
[RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) { [RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
viewManagers[moduleName] = [[moduleClass alloc] init]; viewManagers[moduleName] = [[moduleClass alloc] initWithEventDispatcher:_bridge.eventDispatcher];
}]; }];
_viewManagers = viewManagers; _viewManagers = viewManagers;
@ -535,11 +515,6 @@ static NSDictionary *RCTViewModuleClasses(void)
} }
} }
- (UIView *)viewForViewManager:(id <RCTNativeViewModule>)manager
{
return [manager viewWithEventDispatcher:_bridge.eventDispatcher];
}
static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView, id <RCTNativeViewModule>manager) static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView, id <RCTNativeViewModule>manager)
{ {
// TODO: cache respondsToSelector tests // TODO: cache respondsToSelector tests
@ -581,7 +556,9 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
if (obj == [NSNull null]) { if (obj == [NSNull null]) {
// Copy property from default view to current // 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 { } else {
RCTSetProperty(shadowView, key, obj); 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 // 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 // for this className - this is ok because we only use the default for restoring
// defaults, which never happens on first creation. // 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) { if (view) {
// Set required properties // Set required properties
view.reactTag = reactTag; view.reactTag = reactTag;
@ -1013,7 +990,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}]; }];
} }
- (NSDictionary *)allBubblingEventTypesConfigs + (NSDictionary *)allBubblingEventTypesConfigs
{ {
NSMutableDictionary *customBubblingEventTypesConfigs = [@{ NSMutableDictionary *customBubblingEventTypesConfigs = [@{
// Bubble dispatched events // Bubble dispatched events
@ -1103,20 +1080,20 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}, },
} mutableCopy]; } mutableCopy];
for (id <RCTNativeViewModule> viewManager in _viewManagers.allValues) { [RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
NSDictionary *bubblingEvents = [viewManager respondsToSelector:@selector(customBubblingEventTypes)]? [viewManager customBubblingEventTypes] : nil; if (RCTClassOverridesClassMethod(cls, @selector(customBubblingEventTypes))) {
if (bubblingEvents) { NSDictionary *eventTypes = [cls customBubblingEventTypes];
for (NSString *eventName in bubblingEvents) { for (NSString *eventName in eventTypes) {
RCTCAssert(!customBubblingEventTypesConfigs[eventName], @"Event %@ registered multiple times.", eventName); RCTCAssert(!customBubblingEventTypesConfigs[eventName], @"Event '%@' registered multiple times.", eventName);
}
[customBubblingEventTypesConfigs addEntriesFromDictionary:bubblingEvents];
} }
[customBubblingEventTypesConfigs addEntriesFromDictionary:eventTypes];
} }
}];
return customBubblingEventTypesConfigs; return customBubblingEventTypesConfigs;
} }
- (NSDictionary *)allDirectEventTypesConfigs + (NSDictionary *)allDirectEventTypesConfigs
{ {
NSMutableDictionary *customDirectEventTypes = [@{ NSMutableDictionary *customDirectEventTypes = [@{
@"topScrollBeginDrag": @{ @"topScrollBeginDrag": @{
@ -1154,22 +1131,21 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}, },
} mutableCopy]; } mutableCopy];
for (id <RCTNativeViewModule> viewManager in _viewManagers.allValues) { [RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
NSDictionary *bubblingEvents = [viewManager respondsToSelector:@selector(customDirectEventTypes)] ? [viewManager customDirectEventTypes] : nil; if (RCTClassOverridesClassMethod(cls, @selector(customDirectEventTypes))) {
if (bubblingEvents) { NSDictionary *eventTypes = [cls customDirectEventTypes];
for (NSString *eventName in bubblingEvents) { for (NSString *eventName in eventTypes) {
RCTCAssert(!customDirectEventTypes[eventName], @"Event %@ registered multiple times.", eventName); RCTCAssert(!customDirectEventTypes[eventName], @"Event '%@' registered multiple times.", eventName);
}
[customDirectEventTypes addEntriesFromDictionary:bubblingEvents];
} }
[customDirectEventTypes addEntriesFromDictionary:eventTypes];
} }
}];
return customDirectEventTypes; return customDirectEventTypes;
} }
- (NSDictionary *)constantsToExport + (NSDictionary *)constantsToExport
{ {
UIScreen *screen = [UIScreen mainScreen];
NSMutableDictionary *allJSConstants = [@{ NSMutableDictionary *allJSConstants = [@{
@"customBubblingEventTypes": [self allBubblingEventTypesConfigs], @"customBubblingEventTypes": [self allBubblingEventTypesConfigs],
@"customDirectEventTypes": [self allDirectEventTypesConfigs], @"customDirectEventTypes": [self allDirectEventTypesConfigs],
@ -1180,13 +1156,13 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}, },
@"Dimensions": @{ @"Dimensions": @{
@"window": @{ @"window": @{
@"width": @(screen.bounds.size.width), @"width": @(RCTScreenSize().width),
@"height": @(screen.bounds.size.height), @"height": @(RCTScreenSize().height),
@"scale": @(screen.scale), @"scale": @(RCTScreenScale()),
}, },
@"modalFullscreenView": @{ @"modalFullscreenView": @{
@"width": @(screen.bounds.size.width), @"width": @(RCTScreenSize().width),
@"height": @(screen.bounds.size.height), @"height": @(RCTScreenSize().width),
}, },
}, },
@"StyleConstants": @{ @"StyleConstants": @{
@ -1224,12 +1200,15 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}, },
} mutableCopy]; } mutableCopy];
[_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, id <RCTNativeViewModule> viewManager, BOOL *stop) { [RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
NSDictionary *constants = [viewManager respondsToSelector:@selector(constantsToExport)] ? [viewManager constantsToExport] : nil; // TODO: should these be inherited?
if (constants) { NSDictionary *constants = RCTClassOverridesClassMethod(cls, @selector(constantsToExport)) ? [cls constantsToExport] : nil;
RCTAssert(allJSConstants[name] == nil , @"Cannot redefine constant namespace: %@", name); 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 // add an additional 'Constants' namespace for each class
allJSConstants[name] = @{@"Constants": constants}; namespace[@"Constants"] = constants;
allJSConstants[name] = [namespace copy];
} }
}]; }];

View File

@ -50,7 +50,6 @@
83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */; }; 83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */; };
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */; }; 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */; };
83CBBA871A60202500E9B192 /* RCTJavaScriptAppEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA861A60202500E9B192 /* RCTJavaScriptAppEngine.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 */; }; 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */; };
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBACB1A6023D300E9B192 /* RCTConvert.m */; }; 83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBACB1A6023D300E9B192 /* RCTConvert.m */; };
83EEC2EE1A604AB200C39218 /* RCTModuleMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 83EEC2ED1A604AB200C39218 /* RCTModuleMethod.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 = "<group>"; }; 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTEventDispatcher.m; sourceTree = "<group>"; };
83CBBA851A60202500E9B192 /* RCTJavaScriptAppEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptAppEngine.h; sourceTree = "<group>"; }; 83CBBA851A60202500E9B192 /* RCTJavaScriptAppEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptAppEngine.h; sourceTree = "<group>"; };
83CBBA861A60202500E9B192 /* RCTJavaScriptAppEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptAppEngine.m; sourceTree = "<group>"; }; 83CBBA861A60202500E9B192 /* RCTJavaScriptAppEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptAppEngine.m; sourceTree = "<group>"; };
83CBBA891A60204600E9B192 /* RCTModuleIDs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleIDs.h; sourceTree = "<group>"; };
83CBBA8A1A60204600E9B192 /* RCTModuleIDs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleIDs.m; sourceTree = "<group>"; };
83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTouchHandler.h; sourceTree = "<group>"; }; 83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTouchHandler.h; sourceTree = "<group>"; };
83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTouchHandler.m; sourceTree = "<group>"; }; 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTouchHandler.m; sourceTree = "<group>"; };
83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTConvert.h; sourceTree = "<group>"; }; 83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTConvert.h; sourceTree = "<group>"; };
@ -329,8 +326,6 @@
83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */, 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */,
83CBBA4D1A601E3B00E9B192 /* RCTLog.h */, 83CBBA4D1A601E3B00E9B192 /* RCTLog.h */,
83CBBA4E1A601E3B00E9B192 /* RCTLog.m */, 83CBBA4E1A601E3B00E9B192 /* RCTLog.m */,
83CBBA891A60204600E9B192 /* RCTModuleIDs.h */,
83CBBA8A1A60204600E9B192 /* RCTModuleIDs.m */,
83CBBA581A601E9000E9B192 /* RCTRedBox.h */, 83CBBA581A601E9000E9B192 /* RCTRedBox.h */,
83CBBA591A601E9000E9B192 /* RCTRedBox.m */, 83CBBA591A601E9000E9B192 /* RCTRedBox.m */,
83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */, 83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */,
@ -439,7 +434,6 @@
13E067591A70F44B002CDEE1 /* UIView+ReactKit.m in Sources */, 13E067591A70F44B002CDEE1 /* UIView+ReactKit.m in Sources */,
137029531A69923600575408 /* RCTImageDownloader.m in Sources */, 137029531A69923600575408 /* RCTImageDownloader.m in Sources */,
83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */, 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */,
83CBBA8B1A60204600E9B192 /* RCTModuleIDs.m in Sources */,
83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */, 83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */,
13B080071A6947C200A75B9A /* RCTShadowRawText.m in Sources */, 13B080071A6947C200A75B9A /* RCTShadowRawText.m in Sources */,
13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */, 13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */,
@ -555,6 +549,7 @@
"DEBUG=1", "DEBUG=1",
"$(inherited)", "$(inherited)",
); );
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
@ -564,6 +559,7 @@
83CBBA411A601D0F00E9B192 /* Release */ = { 83CBBA411A601D0F00E9B192 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;

View File

@ -7,9 +7,9 @@
@implementation RCTNavItemManager @implementation RCTNavItemManager
- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher - (UIView *)view
{ {
return [[RCTNavItem alloc] initWithFrame:CGRectZero]; return [[RCTNavItem alloc] init];
} }
RCT_EXPORT_VIEW_PROPERTY(title) RCT_EXPORT_VIEW_PROPERTY(title)

View File

@ -9,8 +9,7 @@
@property (nonatomic, strong) UIView *reactNavSuperviewLink; @property (nonatomic, strong) UIView *reactNavSuperviewLink;
@property (nonatomic, assign) NSInteger requestedTopOfStack; @property (nonatomic, assign) NSInteger requestedTopOfStack;
- (instancetype)initWithFrame:(CGRect)frame - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
eventDispatcher:(RCTEventDispatcher *)eventDispatcher;
/** /**
* Schedules a JavaScript navigation and prevents `UIKit` from navigating until * Schedules a JavaScript navigation and prevents `UIKit` from navigating until

View File

@ -126,7 +126,17 @@ NSInteger kNeverProgressed = -10000;
*/ */
@implementation RCTNavigationController @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(); RCT_NOT_DESIGNATED_INITIALIZER();
} }
@ -137,7 +147,7 @@ NSInteger kNeverProgressed = -10000;
*/ */
- (instancetype)initWithScrollCallback:(dispatch_block_t)callback - (instancetype)initWithScrollCallback:(dispatch_block_t)callback
{ {
if ((self = [super init])) { if ((self = [super initWithNibName:nil bundle:nil])) {
_scrollCallback = callback; _scrollCallback = callback;
} }
return self; return self;
@ -265,11 +275,14 @@ NSInteger kNeverProgressed = -10000;
@implementation RCTNavigator @implementation RCTNavigator
- (id)initWithFrame:(CGRect)frame - (instancetype)initWithFrame:(CGRect)frame
eventDispatcher:(RCTEventDispatcher *)eventDispatcher
{ {
self = [super initWithFrame:frame]; RCT_NOT_DESIGNATED_INITIALIZER();
if (self) { }
- (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithFrame:CGRectZero])) {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(reportNavigationProgress:)]; _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(reportNavigationProgress:)];
_mostRecentProgress = kNeverProgressed; _mostRecentProgress = kNeverProgressed;
_dummyView = [[UIView alloc] initWithFrame:CGRectZero]; _dummyView = [[UIView alloc] initWithFrame:CGRectZero];
@ -311,24 +324,15 @@ NSInteger kNeverProgressed = -10000;
return; return;
} }
_mostRecentProgress = nextProgress; _mostRecentProgress = nextProgress;
[_eventDispatcher sendRawEventWithType:@"topNavigationProgress" [_eventDispatcher sendEventWithName:@"topNavigationProgress" body:@{
body:@{@"fromIndex": @(_currentlyTransitioningFrom), @"fromIndex": @(_currentlyTransitioningFrom),
@"toIndex": @(_currentlyTransitioningTo), @"toIndex": @(_currentlyTransitioningTo),
@"progress": @(nextProgress), @"progress": @(nextProgress),
@"target": self.reactTag}]; @"target": self.reactTag
}];
} }
} }
- (instancetype)initWithFrame:(CGRect)frame
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (instancetype)init
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (void)dealloc - (void)dealloc
{ {
_navigationController.delegate = nil; _navigationController.delegate = nil;
@ -438,9 +442,10 @@ NSInteger kNeverProgressed = -10000;
- (void)handleTopOfStackChanged - (void)handleTopOfStackChanged
{ {
[_eventDispatcher sendRawEventWithType:@"topNavigateBack" [_eventDispatcher sendEventWithName:@"topNavigateBack" body:@{
body:@{@"target":self.reactTag, @"target":self.reactTag,
@"stackLength":@(_navigationController.viewControllers.count)}]; @"stackLength":@(_navigationController.viewControllers.count)
}];
} }
- (void)dispatchFakeScrollEvent - (void)dispatchFakeScrollEvent

View File

@ -8,9 +8,9 @@
@implementation RCTNavigatorManager @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) RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack)

View File

@ -4,6 +4,7 @@
#import "RCTImageDownloader.h" #import "RCTImageDownloader.h"
#import "RCTUtils.h" #import "RCTUtils.h"
#import "RCTConvert.h"
@implementation RCTNetworkImageView @implementation RCTNetworkImageView
{ {
@ -52,6 +53,19 @@
self.layer.contentsScale = _defaultImage.scale; self.layer.contentsScale = _defaultImage.scale;
self.layer.contents = (__bridge id)_defaultImage.CGImage; self.layer.contents = (__bridge id)_defaultImage.CGImage;
} }
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) { _downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() block:^(UIImage *image, NSError *error) {
if (image) { if (image) {
self.layer.contentsScale = image.scale; self.layer.contentsScale = image.scale;
@ -61,6 +75,7 @@
}]; }];
} }
} }
}
- (void)setImageURL:(NSURL *)imageURL - (void)setImageURL:(NSURL *)imageURL
{ {

View File

@ -11,7 +11,7 @@
@implementation RCTNetworkImageViewManager @implementation RCTNetworkImageViewManager
- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher - (UIView *)view
{ {
RCTNetworkImageView *view = [[RCTNetworkImageView alloc] initWithFrame:CGRectZero imageDownloader:[RCTImageDownloader sharedInstance]]; RCTNetworkImageView *view = [[RCTNetworkImageView alloc] initWithFrame:CGRectZero imageDownloader:[RCTImageDownloader sharedInstance]];
view.contentMode = UIViewContentModeScaleAspectFill; view.contentMode = UIViewContentModeScaleAspectFill;

View File

@ -6,7 +6,7 @@
@implementation RCTRawTextManager @implementation RCTRawTextManager
- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher - (UIView *)view
{ {
return [[UIView alloc] init]; return [[UIView alloc] init];
} }

View File

@ -32,6 +32,6 @@
@property (nonatomic, assign) BOOL centerContent; @property (nonatomic, assign) BOOL centerContent;
@property (nonatomic, copy) NSArray *stickyHeaderIndices; @property (nonatomic, copy) NSArray *stickyHeaderIndices;
- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTEventDispatcher *)eventDispatcher; - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
@end @end

View File

@ -8,6 +8,7 @@
#import "RCTEventDispatcher.h" #import "RCTEventDispatcher.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTUIManager.h" #import "RCTUIManager.h"
#import "RCTUtils.h"
#import "UIView+ReactKit.h" #import "UIView+ReactKit.h"
CGFloat const ZINDEX_DEFAULT = 0; CGFloat const ZINDEX_DEFAULT = 0;
@ -122,6 +123,7 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
*/ */
- (BOOL)touchesShouldCancelInContentView:(UIView *)view - (BOOL)touchesShouldCancelInContentView:(UIView *)view
{ {
//TODO: shouldn't this call super if _shouldDisableScrollInteraction returns NO?
return ![self _shouldDisableScrollInteraction]; return ![self _shouldDisableScrollInteraction];
} }
@ -260,9 +262,14 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
@synthesize nativeMainScrollDelegate = _nativeMainScrollDelegate; @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; _eventDispatcher = eventDispatcher;
_scrollView = [[RCTCustomScrollView alloc] initWithFrame:CGRectZero]; _scrollView = [[RCTCustomScrollView alloc] initWithFrame:CGRectZero];

View File

@ -7,9 +7,9 @@
@implementation RCTScrollViewManager @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) RCT_EXPORT_VIEW_PROPERTY(alwaysBounceHorizontal)
@ -36,7 +36,7 @@ RCT_EXPORT_VIEW_PROPERTY(contentInset);
RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets); RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets);
RCT_EXPORT_VIEW_PROPERTY(contentOffset); RCT_EXPORT_VIEW_PROPERTY(contentOffset);
- (NSDictionary *)constantsToExport + (NSDictionary *)constantsToExport
{ {
return return
@{ @{

View File

@ -9,14 +9,26 @@
@implementation RCTStaticImageManager @implementation RCTStaticImageManager
- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher - (UIView *)view
{ {
return [[RCTStaticImage alloc] init]; return [[RCTStaticImage alloc] init];
} }
RCT_REMAP_VIEW_PROPERTY(src, image)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode) 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 - (void)set_capInsets:(id)json forView:(RCTStaticImage *)view withDefaultView:(RCTStaticImage *)defaultView
{ {
view.capInsets = json ? [RCTConvert UIEdgeInsets:json] : defaultView.capInsets; view.capInsets = json ? [RCTConvert UIEdgeInsets:json] : defaultView.capInsets;

View File

@ -10,6 +10,6 @@
@property (nonatomic, assign) BOOL autoCorrect; @property (nonatomic, assign) BOOL autoCorrect;
@property (nonatomic, assign) UIEdgeInsets paddingEdgeInsets; // TODO: contentInset @property (nonatomic, assign) UIEdgeInsets paddingEdgeInsets; // TODO: contentInset
- (instancetype)initWithFrame:(CGRect)frame eventDispatcher:(RCTEventDispatcher *)eventDispatcher; - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
@end @end

View File

@ -14,14 +14,14 @@
BOOL _jsRequestingFirstResponder; BOOL _jsRequestingFirstResponder;
} }
- (instancetype)init - (instancetype)initWithFrame:(CGRect)frame
{ {
RCT_NOT_DESIGNATED_INITIALIZER(); 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; _eventDispatcher = eventDispatcher;
[self addTarget:self action:@selector(_textFieldDidChange) forControlEvents:UIControlEventEditingChanged]; [self addTarget:self action:@selector(_textFieldDidChange) forControlEvents:UIControlEventEditingChanged];

View File

@ -8,9 +8,9 @@
@implementation RCTTextFieldManager @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) RCT_EXPORT_VIEW_PROPERTY(caretHidden)

View File

@ -13,7 +13,7 @@
@implementation RCTTextManager @implementation RCTTextManager
- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher - (UIView *)view
{ {
UILabel *label = [[UILabel alloc] init]; UILabel *label = [[UILabel alloc] init];
label.numberOfLines = 0; label.numberOfLines = 0;

View File

@ -6,9 +6,9 @@
@implementation RCTUIActivityIndicatorViewManager @implementation RCTUIActivityIndicatorViewManager
- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher - (UIView *)view
{ {
return [[UIActivityIndicatorView alloc] initWithFrame:CGRectZero]; return [[UIActivityIndicatorView alloc] init];
} }
RCT_EXPORT_VIEW_PROPERTY(activityIndicatorViewStyle) RCT_EXPORT_VIEW_PROPERTY(activityIndicatorViewStyle)
@ -28,7 +28,7 @@ RCT_EXPORT_VIEW_PROPERTY(color)
} }
} }
- (NSDictionary *)constantsToExport + (NSDictionary *)constantsToExport
{ {
return return
@{ @{

View File

@ -4,6 +4,12 @@
#import "RCTExport.h" #import "RCTExport.h"
@class RCTEventDispatcher;
@interface RCTUIViewManager : NSObject <RCTNativeViewModule> @interface RCTUIViewManager : NSObject <RCTNativeViewModule>
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
@end @end

View File

@ -3,13 +3,38 @@
#import "RCTUIViewManager.h" #import "RCTUIViewManager.h"
#import "RCTConvert.h" #import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTShadowView.h" #import "RCTShadowView.h"
#import "RCTView.h" #import "RCTView.h"
@implementation RCTUIViewManager @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]; 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(borderColor, layer.borderColor);
RCT_REMAP_VIEW_PROPERTY(borderRadius, layer.cornerRadius) RCT_REMAP_VIEW_PROPERTY(borderRadius, layer.cornerRadius)
RCT_REMAP_VIEW_PROPERTY(borderWidth, layer.borderWidth) 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 - (void)set_overflow:(id)json
forView:(UIView *)view forView:(UIView *)view

View File

@ -6,12 +6,11 @@
@implementation RCTViewManager @implementation RCTViewManager
- (UIView *)viewWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher - (UIView *)view
{ {
return [[RCTView alloc] init]; return [[RCTView alloc] init];
} }
RCT_EXPORT_VIEW_PROPERTY(accessibilityLabel)
RCT_EXPORT_VIEW_PROPERTY(pointerEvents) RCT_EXPORT_VIEW_PROPERTY(pointerEvents)
@end @end

View File

@ -96,7 +96,7 @@
- (void)rightButtonTapped - (void)rightButtonTapped
{ {
[_eventDispatcher sendRawEventWithType:@"topNavRightButtonTap" body:@{@"target":_navItem.reactTag}]; [_eventDispatcher sendEventWithName:@"topNavRightButtonTap" body:@{@"target":_navItem.reactTag}];
} }
- (void)didMoveToParentViewController:(UIViewController *)parent - (void)didMoveToParentViewController:(UIViewController *)parent