Second Update from Tue 24 Mar

This commit is contained in:
Christopher Chedeau
2015-03-24 19:34:12 -07:00
parent 8068c65f12
commit ead3a740ca
121 changed files with 6808 additions and 667 deletions

View File

@@ -26,6 +26,9 @@
+ (float)float:(id)json;
+ (int)int:(id)json;
+ (int64_t)int64_t:(id)json;
+ (uint64_t)uint64_t:(id)json;
+ (NSInteger)NSInteger:(id)json;
+ (NSUInteger)NSUInteger:(id)json;
@@ -70,6 +73,7 @@
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json size:(id)json weight:(id)json;
+ (NSArray *)NSStringArray:(id)json;
+ (NSArray *)NSURLArray:(id)json;
+ (NSArray *)NSNumberArray:(id)json;
+ (NSArray *)UIColorArray:(id)json;
+ (NSArray *)CGColorArray:(id)json;
@@ -142,6 +146,14 @@ id RCTConvertValue(id target, NSString *keypath, id json);
#define RCT_CONVERTER(type, name, getter) \
RCT_CONVERTER_CUSTOM(type, name, [json getter])
/**
* This macro is similar to RCT_CONVERTER, but specifically geared towards
* numeric types. It will handle string input correctly, and provides more
* detailed error reporting if a wrong value is passed in.
*/
#define RCT_NUMBER_CONVERTER(type, getter) \
RCT_CONVERTER_CUSTOM(type, type, [[self NSNumber:json] getter])
/**
* This macro is used for creating converters for enum types.
*/
@@ -177,7 +189,7 @@ RCT_CONVERTER_CUSTOM(type, name, [json getter])
* This macro is used for creating converter functions for structs that consist
* of a number of CGFloat properties, such as CGPoint, CGRect, etc.
*/
#define RCT_CGSTRUCT_CONVERTER(type, values) \
#define RCT_CGSTRUCT_CONVERTER(type, values, _aliases) \
+ (type)type:(id)json \
{ \
@try { \
@@ -194,12 +206,23 @@ RCT_CONVERTER_CUSTOM(type, name, [json getter])
RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); \
} else { \
for (NSUInteger i = 0; i < count; i++) { \
((CGFloat *)&result)[i] = [json[i] doubleValue]; \
((CGFloat *)&result)[i] = [self CGFloat:json[i]]; \
} \
} \
} else if ([json isKindOfClass:[NSDictionary class]]) { \
NSDictionary *aliases = _aliases; \
if (aliases.count) { \
json = [json mutableCopy]; \
for (NSString *alias in aliases) { \
NSString *key = aliases[alias]; \
NSNumber *number = json[key]; \
if (number) { \
((NSMutableDictionary *)json)[key] = number; \
} \
} \
} \
for (NSUInteger i = 0; i < count; i++) { \
((CGFloat *)&result)[i] = [json[fields[i]] doubleValue]; \
((CGFloat *)&result)[i] = [self CGFloat:json[fields[i]]]; \
} \
} else if (json && json != [NSNull null]) { \
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", #type, [json class], json); \

View File

@@ -13,25 +13,39 @@
#import "RCTLog.h"
CGFloat const RCTDefaultFontSize = 14;
NSString *const RCTDefaultFontName = @"HelveticaNeue";
NSString *const RCTDefaultFontWeight = @"normal";
NSString *const RCTBoldFontWeight = @"bold";
@implementation RCTConvert
RCT_CONVERTER(BOOL, BOOL, boolValue)
RCT_CONVERTER(double, double, doubleValue)
RCT_CONVERTER(float, float, floatValue)
RCT_CONVERTER(int, int, intValue)
RCT_NUMBER_CONVERTER(double, doubleValue)
RCT_NUMBER_CONVERTER(float, floatValue)
RCT_NUMBER_CONVERTER(int, intValue)
RCT_CONVERTER(NSInteger, NSInteger, integerValue)
RCT_CONVERTER_CUSTOM(NSUInteger, NSUInteger, [json unsignedIntegerValue])
RCT_NUMBER_CONVERTER(int64_t, longLongValue);
RCT_NUMBER_CONVERTER(uint64_t, unsignedLongLongValue);
RCT_NUMBER_CONVERTER(NSInteger, integerValue)
RCT_NUMBER_CONVERTER(NSUInteger, unsignedIntegerValue)
RCT_CONVERTER_CUSTOM(NSArray *, NSArray, [NSArray arrayWithArray:json])
RCT_CONVERTER_CUSTOM(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json])
RCT_CONVERTER(NSString *, NSString, description)
RCT_CONVERTER_CUSTOM(NSNumber *, NSNumber, @([json doubleValue]))
+ (NSNumber *)NSNumber:(id)json
{
if ([json isKindOfClass:[NSNumber class]]) {
return json;
} else if ([json isKindOfClass:[NSString class]]) {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSNumber *number = [formatter numberFromString:json];
if (!number) {
RCTLogError(@"JSON String '%@' could not be interpreted as a number", json);
}
return number;
} else if (json && json != [NSNull null]) {
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a number", json, [json class]);
}
return nil;
}
+ (NSURL *)NSURL:(id)json
{
@@ -47,7 +61,12 @@ RCT_CONVERTER_CUSTOM(NSNumber *, NSNumber, @([json doubleValue]))
}
else if ([path length])
{
return [NSURL URLWithString:path relativeToURL:[[NSBundle mainBundle] resourceURL]];
NSURL *URL = [NSURL URLWithString:path relativeToURL:[[NSBundle mainBundle] resourceURL]];
if ([URL isFileURL] &&![[NSFileManager defaultManager] fileExistsAtPath:[URL absoluteString]]) {
RCTLogWarn(@"The file '%@' does not exist", URL);
return nil;
}
return URL;
}
return nil;
}
@@ -58,11 +77,11 @@ RCT_CONVERTER_CUSTOM(NSNumber *, NSNumber, @([json doubleValue]))
}
// JS Standard for time is milliseconds
RCT_CONVERTER_CUSTOM(NSDate *, NSDate, [NSDate dateWithTimeIntervalSince1970:[json doubleValue] / 1000.0])
RCT_CONVERTER_CUSTOM(NSTimeInterval, NSTimeInterval, [json doubleValue] / 1000.0)
RCT_CONVERTER_CUSTOM(NSDate *, NSDate, [NSDate dateWithTimeIntervalSince1970:[self double:json] / 1000.0])
RCT_CONVERTER_CUSTOM(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0)
// JS standard for time zones is minutes.
RCT_CONVERTER_CUSTOM(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[json doubleValue] * 60.0])
RCT_CONVERTER_CUSTOM(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
RCT_ENUM_CONVERTER(NSTextAlignment, (@{
@"auto": @(NSTextAlignmentNatural),
@@ -90,11 +109,12 @@ RCT_ENUM_CONVERTER(UIKeyboardType, (@{
@"default": @(UIKeyboardTypeDefault),
}), UIKeyboardTypeDefault, integerValue)
RCT_CONVERTER(CGFloat, CGFloat, doubleValue)
RCT_CGSTRUCT_CONVERTER(CGPoint, (@[@"x", @"y"]))
RCT_CGSTRUCT_CONVERTER(CGSize, (@[@"w", @"h"]))
RCT_CGSTRUCT_CONVERTER(CGRect, (@[@"x", @"y", @"w", @"h"]))
RCT_CGSTRUCT_CONVERTER(UIEdgeInsets, (@[@"top", @"left", @"bottom", @"right"]))
// TODO: normalise the use of w/width so we can do away with the alias values (#6566645)
RCT_CONVERTER_CUSTOM(CGFloat, CGFloat, [self double:json])
RCT_CGSTRUCT_CONVERTER(CGPoint, (@[@"x", @"y"]), nil)
RCT_CGSTRUCT_CONVERTER(CGSize, (@[@"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
RCT_CGSTRUCT_CONVERTER(CGRect, (@[@"x", @"y", @"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
RCT_CGSTRUCT_CONVERTER(UIEdgeInsets, (@[@"top", @"left", @"bottom", @"right"]), nil)
RCT_ENUM_CONVERTER(CGLineJoin, (@{
@"miter": @(kCGLineJoinMiter),
@@ -113,9 +133,9 @@ RCT_CGSTRUCT_CONVERTER(CATransform3D, (@[
@"m21", @"m22", @"m23", @"m24",
@"m31", @"m32", @"m33", @"m34",
@"m41", @"m42", @"m43", @"m44"
]))
]), nil)
RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty"]))
RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty"]), nil)
+ (UIColor *)UIColor:(id)json
{
@@ -328,19 +348,19 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty
} else {
// Color array
color = [UIColor colorWithRed:[json[0] doubleValue]
green:[json[1] doubleValue]
blue:[json[2] doubleValue]
alpha:[json count] > 3 ? [json[3] doubleValue] : 1];
color = [UIColor colorWithRed:[self double:json[0]]
green:[self double:json[1]]
blue:[self double:json[2]]
alpha:[json count] > 3 ? [self double:json[3]] : 1];
}
} else if ([json isKindOfClass:[NSDictionary class]]) {
// Color dictionary
color = [UIColor colorWithRed:[json[@"r"] doubleValue]
green:[json[@"g"] doubleValue]
blue:[json[@"b"] doubleValue]
alpha:[json[@"a"] ?: @1 doubleValue]];
color = [UIColor colorWithRed:[self double:json[@"r"]]
green:[self double:json[@"g"]]
blue:[self double:json[@"b"]]
alpha:[self double:json[@"a"] ?: @1]];
} else if (json && ![json isKindOfClass:[NSNull class]]) {
@@ -415,6 +435,11 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)family size:(id)size weight:(id)weight
{
CGFloat const RCTDefaultFontSize = 14;
NSString *const RCTDefaultFontName = @"HelveticaNeue";
NSString *const RCTDefaultFontWeight = @"normal";
NSString *const RCTBoldFontWeight = @"bold";
// Create descriptor
UIFontDescriptor *fontDescriptor = font.fontDescriptor ?: [UIFontDescriptor fontDescriptorWithName:RCTDefaultFontName size:RCTDefaultFontSize];
@@ -427,7 +452,7 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty
// Get font family
NSString *familyName = [self NSString:family];
if (familyName) {
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
font = [UIFont fontWithName:familyName size:fontDescriptor.pointSize];
if (font) {
// It's actually a font name, not a font family name,
@@ -437,11 +462,14 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty
} else {
// Not a valid font or family
RCTLogError(@"Unrecognized font family '%@'", familyName);
familyName = [UIFont fontWithDescriptor:fontDescriptor size:0].familyName;
}
} else {
// Set font family
fontDescriptor = [fontDescriptor fontDescriptorWithFamily:familyName];
}
} else {
familyName = [UIFont fontWithDescriptor:fontDescriptor size:0].familyName;
}
// Get font weight
@@ -451,29 +479,43 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty
static NSSet *values;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
values = [NSSet setWithObjects:@"bold", @"normal", nil];
values = [NSSet setWithObjects:RCTDefaultFontWeight, RCTBoldFontWeight, nil];
});
if (fontWeight && ![values containsObject:fontWeight]) {
RCTLogError(@"Unrecognized font weight '%@', must be one of %@", fontWeight, values);
fontWeight = RCTDefaultFontWeight;
}
UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits;
// this is hacky. we are appending the string -Medium because most fonts we currently use
// just need to have -Medium appended to get the bold we want. we're going to revamp this
// to make it easier to know which options are available in JS. t4996115
if ([fontWeight isEqualToString:RCTBoldFontWeight]) {
symbolicTraits |= UIFontDescriptorTraitBold;
} else {
symbolicTraits &= ~UIFontDescriptorTraitBold;
font = nil;
for (NSString *fontName in [UIFont fontNamesForFamilyName:familyName]) {
if ([fontName hasSuffix:@"-Medium"]) {
font = [UIFont fontWithName:fontName size:fontDescriptor.pointSize];
break;
}
if ([fontName hasSuffix:@"-Bold"]) {
font = [UIFont fontWithName:fontName size:fontDescriptor.pointSize];
// But keep searching in case there's a medium option
}
}
if (font) {
fontDescriptor = font.fontDescriptor;
}
}
fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits];
}
// TODO: font style
// Create font
return [UIFont fontWithDescriptor:fontDescriptor size:fontDescriptor.pointSize];
return [UIFont fontWithDescriptor:fontDescriptor size:0];
}
RCT_ARRAY_CONVERTER(NSString)
RCT_ARRAY_CONVERTER(NSURL)
RCT_ARRAY_CONVERTER(NSNumber)
RCT_ARRAY_CONVERTER(UIColor)
@@ -729,6 +771,9 @@ static id RCTConvertValueWithExplicitEncoding(id target, NSString *key, id json,
@"extAlignment": ^(id val) {
return [RCTConvert NSTextAlignment:val];
},
@"ritingDirection": ^(id val) {
return [RCTConvert NSWritingDirection:val];
},
@"Cap": ^(id val) {
return [RCTConvert CGLineCap:val];
},

View File

@@ -11,7 +11,7 @@
#import "RCTBridge.h"
@interface RCTRootView : UIView
@interface RCTRootView : UIView<RCTInvalidating>
/**
* The URL of the bundled application script (required).

View File

@@ -29,6 +29,7 @@ NSString *const RCTReloadNotification = @"RCTReloadNotification";
RCTBridge *_bridge;
RCTTouchHandler *_touchHandler;
id<RCTJavaScriptExecutor> _executor;
BOOL _registered;
}
static Class _globalExecutorClass;
@@ -36,7 +37,7 @@ static Class _globalExecutorClass;
+ (void)initialize
{
#if DEBUG
#if TARGET_IPHONE_SIMULATOR
// Register Cmd-R as a global refresh key
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
@@ -106,12 +107,32 @@ static Class _globalExecutorClass;
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
args:@[self.reactTag]];
[self invalidate];
}
#pragma mark - RCTInvalidating
- (BOOL)isValid
{
return [_bridge isValid];
}
- (void)invalidate
{
// Clear view
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self removeGestureRecognizer:_touchHandler];
[_touchHandler invalidate];
[_executor invalidate];
// TODO: eventually we'll want to be able to share the bridge between
// multiple rootviews, in which case we'll need to move this elsewhere
[_bridge invalidate];
}
#pragma mark Bundle loading
- (void)bundleFinishedLoading:(NSError *)error
{
if (error != nil) {
@@ -124,6 +145,7 @@ static Class _globalExecutorClass;
} else {
[_bridge.uiManager registerRootView:self];
_registered = YES;
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@@ -137,8 +159,7 @@ static Class _globalExecutorClass;
- (void)loadBundle
{
// Clear view
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self invalidate];
if (!_scriptURL) {
return;
@@ -150,6 +171,8 @@ static Class _globalExecutorClass;
[_executor invalidate];
[_bridge invalidate];
_registered = NO;
// Choose local executor if specified, followed by global, followed by default
_executor = [[_executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class] alloc] init];
_bridge = [[RCTBridge alloc] initWithExecutor:_executor moduleProvider:_moduleProvider];
@@ -209,15 +232,20 @@ static Class _globalExecutorClass;
[self bundleFinishedLoading:error];
return;
}
if (!_bridge.isValid) {
return; // Bridge was invalidated in the meanwhile
}
// Success!
RCTSourceCode *sourceCodeModule = _bridge.modules[NSStringFromClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = _scriptURL;
sourceCodeModule.scriptText = rawText;
[_bridge enqueueApplicationScript:rawText url:_scriptURL onComplete:^(NSError *error) {
[_bridge enqueueApplicationScript:rawText url:_scriptURL onComplete:^(NSError *_error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self bundleFinishedLoading:error];
if (_bridge.isValid) {
[self bundleFinishedLoading:_error];
}
});
}];
@@ -236,9 +264,12 @@ static Class _globalExecutorClass;
[self loadBundle];
}
- (BOOL)isReactRootView
- (void)layoutSubviews
{
return YES;
[super layoutSubviews];
if (_registered) {
[_bridge.uiManager setFrame:self.frame forRootView:self];
}
}
- (void)reload

View File

@@ -129,13 +129,15 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) {
// Find closest React-managed touchable view
UIView *targetView = touch.view;
while (targetView) {
if (targetView.reactTag && targetView.userInteractionEnabled) { // TODO: implement respondsToTouch: mechanism
if (targetView.reactTag && targetView.userInteractionEnabled &&
[targetView reactRespondsToTouch:touch]) {
break;
}
targetView = targetView.superview;
}
if (!targetView.reactTag || !targetView.userInteractionEnabled) {
NSNumber *reactTag = [targetView reactTagAtPoint:[touch locationInView:targetView]];
if (!reactTag || !targetView.userInteractionEnabled) {
return;
}
@@ -155,7 +157,7 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) {
// Create touch
NSMutableDictionary *reactTouch = [[NSMutableDictionary alloc] initWithCapacity:9];
reactTouch[@"target"] = [targetView reactTagAtPoint:[touch locationInView:targetView]];
reactTouch[@"target"] = reactTag;
reactTouch[@"identifier"] = @(touchID);
reactTouch[@"touches"] = [NSNull null]; // We hijack this touchObj to serve both as an event
reactTouch[@"changedTouches"] = [NSNull null]; // and as a Touch object, so making this JIT friendly.
@@ -248,15 +250,15 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) {
if (_recordingInteractionTiming) {
[_bridgeInteractionTiming addObject:@{
@"timeSeconds": @(touch.originatingTime),
@"operation": @"taskOriginated",
@"taskID": @(touch.id),
}];
@"timeSeconds": @(touch.originatingTime),
@"operation": @"taskOriginated",
@"taskID": @(touch.id),
}];
[_bridgeInteractionTiming addObject:@{
@"timeSeconds": @(enqueueTime),
@"operation": @"taskEnqueuedPending",
@"taskID": @(touch.id),
}];
@"timeSeconds": @(enqueueTime),
@"operation": @"taskEnqueuedPending",
@"taskID": @(touch.id),
}];
}
}
@@ -273,18 +275,18 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) {
if (_recordingInteractionTiming) {
for (RCTTouchEvent *touch in _pendingTouches) {
[_bridgeInteractionTiming addObject:@{
@"timeSeconds": @(sender.timestamp),
@"operation": @"frameAlignedDispatch",
@"taskID": @(touch.id),
}];
@"timeSeconds": @(sender.timestamp),
@"operation": @"frameAlignedDispatch",
@"taskID": @(touch.id),
}];
}
if (pendingCount > 0 || sender.timestamp - _mostRecentEnqueueJS < 0.1) {
[_bridgeInteractionTiming addObject:@{
@"timeSeconds": @(sender.timestamp),
@"operation": @"mainThreadDisplayLink",
@"taskID": @([RCTTouchEvent newID]),
}];
@"timeSeconds": @(sender.timestamp),
@"operation": @"mainThreadDisplayLink",
@"taskID": @([RCTTouchEvent newID]),
}];
}
}

View File

@@ -31,8 +31,14 @@ id RCTJSONParse(NSString *jsonString, NSError **error)
}
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
if (!jsonData) {
RCTLog(@"RCTJSONParse received the following string, which could not be losslessly converted to UTF8 data: '%@'", jsonString);
jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
if (jsonData) {
RCTLogWarn(@"RCTJSONParse received the following string, which could not be losslessly converted to UTF8 data: '%@'", jsonString);
} else {
// If our backup conversion fails, log the issue so we can see what strings are causing this (t6452813)
RCTLogError(@"RCTJSONParse received the following string, which could not be converted to UTF8 data: '%@'", jsonString);
return nil;
}
}
return [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:error];
}