react-native/React/Base/RCTConvert.m

811 lines
27 KiB
Mathematica
Raw Normal View History

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTConvert.h"
#import <objc/message.h>
#import <CoreText/CoreText.h>
2015-04-21 16:48:29 +00:00
#import "RCTDefines.h"
#import "RCTImageSource.h"
#import "RCTParserUtils.h"
#import "RCTUtils.h"
2015-04-21 16:48:29 +00:00
@implementation RCTConvert
RCT_CONVERTER(id, id, self)
RCT_CONVERTER(BOOL, BOOL, boolValue)
2015-03-25 00:37:03 +00:00
RCT_NUMBER_CONVERTER(double, doubleValue)
RCT_NUMBER_CONVERTER(float, floatValue)
RCT_NUMBER_CONVERTER(int, intValue)
RCT_NUMBER_CONVERTER(int64_t, longLongValue);
RCT_NUMBER_CONVERTER(uint64_t, unsignedLongLongValue);
2015-03-25 00:37:03 +00:00
RCT_NUMBER_CONVERTER(NSInteger, integerValue)
RCT_NUMBER_CONVERTER(NSUInteger, unsignedIntegerValue)
/**
* This macro is used for creating converter functions for directly
* representable json values that require no conversion.
*/
#if RCT_DEBUG
#define RCT_JSON_CONVERTER(type) \
+ (type *)type:(id)json \
{ \
if ([json isKindOfClass:[type class]]) { \
return json; \
} else if (json) { \
RCTLogConvertError(json, @#type); \
} \
return nil; \
2015-03-25 00:37:03 +00:00
}
#else
#define RCT_JSON_CONVERTER(type) \
+ (type *)type:(id)json { return json; }
#endif
RCT_JSON_CONVERTER(NSArray)
RCT_JSON_CONVERTER(NSDictionary)
RCT_JSON_CONVERTER(NSString)
RCT_JSON_CONVERTER(NSNumber)
RCT_CUSTOM_CONVERTER(NSSet *, NSSet, [NSSet setWithArray:json])
RCT_CUSTOM_CONVERTER(NSData *, NSData, [json dataUsingEncoding:NSUTF8StringEncoding])
2015-05-06 17:46:45 +00:00
+ (NSIndexSet *)NSIndexSet:(id)json
{
json = [self NSNumberArray:json];
NSMutableIndexSet *indexSet = [NSMutableIndexSet new];
2015-05-06 17:46:45 +00:00
for (NSNumber *number in json) {
NSInteger index = number.integerValue;
if (RCT_DEBUG && index < 0) {
RCTLogError(@"Invalid index value %lld. Indices must be positive.", (long long)index);
2015-05-06 17:46:45 +00:00
}
[indexSet addIndex:index];
}
return indexSet;
}
+ (NSURL *)NSURL:(id)json
{
NSString *path = [self NSString:json];
if (!path) {
return nil;
}
@try { // NSURL has a history of crashing with bad input, so let's be safe
2015-04-13 16:34:03 +00:00
NSURL *URL = [NSURL URLWithString:path];
if (URL.scheme) { // Was a well-formed absolute URL
return URL;
}
// Check if it has a scheme
if ([path rangeOfString:@":"].location != NSNotFound) {
NSMutableCharacterSet *urlAllowedCharacterSet = [NSMutableCharacterSet new];
[urlAllowedCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLUserAllowedCharacterSet]];
[urlAllowedCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLPasswordAllowedCharacterSet]];
[urlAllowedCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLHostAllowedCharacterSet]];
[urlAllowedCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLPathAllowedCharacterSet]];
[urlAllowedCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLQueryAllowedCharacterSet]];
[urlAllowedCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLFragmentAllowedCharacterSet]];
path = [path stringByAddingPercentEncodingWithAllowedCharacters:urlAllowedCharacterSet];
URL = [NSURL URLWithString:path];
if (URL) {
return URL;
}
}
// Assume that it's a local path
path = path.stringByRemovingPercentEncoding;
if ([path hasPrefix:@"~"]) {
// Path is inside user directory
path = path.stringByExpandingTildeInPath;
} else if (!path.absolutePath) {
// Assume it's a resource path
path = [[NSBundle mainBundle].resourcePath stringByAppendingPathComponent:path];
}
if (!(URL = [NSURL fileURLWithPath:path])) {
RCTLogConvertError(json, @"a valid URL");
}
return URL;
}
@catch (__unused NSException *e) {
RCTLogConvertError(json, @"a valid URL");
return nil;
}
}
RCT_ENUM_CONVERTER(NSURLRequestCachePolicy, (@{
@"default": @(NSURLRequestUseProtocolCachePolicy),
@"reload": @(NSURLRequestReloadIgnoringLocalCacheData),
@"force-cache": @(NSURLRequestReturnCacheDataElseLoad),
@"only-if-cached": @(NSURLRequestReturnCacheDataDontLoad),
}), NSURLRequestUseProtocolCachePolicy, integerValue)
+ (NSURLRequest *)NSURLRequest:(id)json
{
if ([json isKindOfClass:[NSString class]]) {
NSURL *URL = [self NSURL:json];
return URL ? [NSURLRequest requestWithURL:URL] : nil;
}
if ([json isKindOfClass:[NSDictionary class]]) {
NSString *URLString = json[@"uri"] ?: json[@"url"];
NSURL *URL;
NSString *bundleName = json[@"bundle"];
if (bundleName) {
URLString = [NSString stringWithFormat:@"%@.bundle/%@", bundleName, URLString];
}
URL = [self NSURL:URLString];
if (!URL) {
return nil;
}
NSData *body = [self NSData:json[@"body"]];
NSString *method = [self NSString:json[@"method"]].uppercaseString ?: @"GET";
NSURLRequestCachePolicy cachePolicy = [self NSURLRequestCachePolicy:json[@"cache"]];
NSDictionary *headers = [self NSDictionary:json[@"headers"]];
if ([method isEqualToString:@"GET"] && headers == nil && body == nil && cachePolicy == NSURLRequestUseProtocolCachePolicy) {
return [NSURLRequest requestWithURL:URL];
}
if (headers) {
__block BOOL allHeadersAreStrings = YES;
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, id header, BOOL *stop) {
if (![header isKindOfClass:[NSString class]]) {
RCTLogError(@"Values of HTTP headers passed must be of type string. "
"Value of header '%@' is not a string.", key);
allHeadersAreStrings = NO;
*stop = YES;
}
}];
if (!allHeadersAreStrings) {
// Set headers to nil here to avoid crashing later.
headers = nil;
}
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
request.HTTPBody = body;
request.HTTPMethod = method;
request.cachePolicy = cachePolicy;
request.allHTTPHeaderFields = headers;
return [request copy];
}
if (json) {
RCTLogConvertError(json, @"a valid URLRequest");
}
return nil;
}
2015-06-23 12:17:36 +00:00
+ (RCTFileURL *)RCTFileURL:(id)json
{
NSURL *fileURL = [self NSURL:json];
if (!fileURL.fileURL) {
2015-06-23 12:17:36 +00:00
RCTLogError(@"URI must be a local file, '%@' isn't.", fileURL);
return nil;
}
if (![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]) {
RCTLogError(@"File '%@' could not be found.", fileURL);
return nil;
}
return fileURL;
}
2015-03-30 13:07:07 +00:00
+ (NSDate *)NSDate:(id)json
{
if ([json isKindOfClass:[NSNumber class]]) {
return [NSDate dateWithTimeIntervalSince1970:[self NSTimeInterval:json]];
} else if ([json isKindOfClass:[NSString class]]) {
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
2015-03-30 13:07:07 +00:00
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ";
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
formatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
});
NSDate *date = [formatter dateFromString:json];
if (!date) {
RCTLogError(@"JSON String '%@' could not be interpreted as a date. "
"Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json);
2015-03-30 13:07:07 +00:00
}
return date;
} else if (json) {
RCTLogConvertError(json, @"a date");
2015-03-30 13:07:07 +00:00
}
return nil;
}
- Adding locale prop to DatePickerIOS Summary: <!-- Thank you for sending the PR! We appreciate you spending the time to work on these changes. Help us understand your motivation by explaining why you decided to make this change. You can learn more about contributing to React Native here: http://facebook.github.io/react-native/docs/contributing.html Happy contributing! --> While building a React Native application, I've come across the use case of wanting to set a specific locale for DatePickers irrespective of the users OS region setting. Since this is a feature available to native DatePicker components, I think it would be helpful to expose this in React Native as well. Testing can be done by passing a `locale` prop to a DatePickerIOS. Example: ``` <DatePickerIOS date={this.state.date} mode="date" locale="fr_FR" onDateChange={date => this.setState({ date: date })} /> ``` <!-- Help reviewers and the release process by writing your own release notes **INTERNAL and MINOR tagged notes will not be included in the next version's final release notes.** CATEGORY [----------] TYPE [ CLI ] [-------------] LOCATION [ DOCS ] [ BREAKING ] [-------------] [ GENERAl ] [ BUGFIX ] [-{Component}-] [ INTERNAL ] [ ENHANCEMENT ] [ {File} ] [ IOS ] [ FEATURE ] [ {Directory} ] |-----------| [ ANDROID ] [ MINOR ] [ {Framework} ] - | {Message} | [----------] [-------------] [-------------] |-----------| [CATEGORY] [TYPE] [LOCATION] - MESSAGE EXAMPLES: [IOS] [BREAKING] [FlatList] - Change a thing that breaks other things [ANDROID] [BUGFIX] [TextInput] - Did a thing to TextInput [CLI] [FEATURE] [local-cli/info/info.js] - CLI easier to do things with [DOCS] [BUGFIX] [GettingStarted.md] - Accidentally a thing/word [GENERAL] [ENHANCEMENT] [Yoga] - Added new yoga thing/position [INTERNAL] [FEATURE] [./scripts] - Added thing to script that nobody will see --> [IOS][ENHANCEMENT][DatePickerIOS] - Adding a locale prop. Closes https://github.com/facebook/react-native/pull/16639 Differential Revision: D6241981 Pulled By: hramos fbshipit-source-id: 77b1b85c09f3e12d6b3e103b3d1ffd1f12e2cea9
2017-11-04 21:32:07 +00:00
+ (NSLocale *)NSLocale:(id)json
{
if ([json isKindOfClass:[NSString class]]) {
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:json];
if (!locale) {
RCTLogError(@"JSON String '%@' could not be interpreted as a valid locale. ", json);
}
return locale;
} else if (json) {
RCTLogConvertError(json, @"a locale");
}
return nil;
}
2015-03-04 01:27:33 +00:00
// JS Standard for time is milliseconds
2015-03-30 13:07:07 +00:00
RCT_CUSTOM_CONVERTER(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0)
2015-03-04 01:27:33 +00:00
// JS standard for time zones is minutes.
2015-03-30 13:07:07 +00:00
RCT_CUSTOM_CONVERTER(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
NSNumber *RCTConvertEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json)
{
if (!json) {
return defaultValue;
}
if ([json isKindOfClass:[NSNumber class]]) {
NSArray *allValues = mapping.allValues;
if ([allValues containsObject:json] || [json isEqual:defaultValue]) {
return json;
}
RCTLogError(@"Invalid %s '%@'. should be one of: %@", typeName, json, allValues);
return defaultValue;
}
if (RCT_DEBUG && ![json isKindOfClass:[NSString class]]) {
RCTLogError(@"Expected NSNumber or NSString for %s, received %@: %@",
typeName, [json classForCoder], json);
}
id value = mapping[json];
if (RCT_DEBUG && !value && [json description].length > 0) {
RCTLogError(@"Invalid %s '%@'. should be one of: %@", typeName, json, [[mapping allKeys] sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)]);
}
return value ?: defaultValue;
}
NSNumber *RCTConvertMultiEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json)
{
if ([json isKindOfClass:[NSArray class]]) {
if ([json count] == 0) {
return defaultValue;
}
long long result = 0;
for (id arrayElement in json) {
NSNumber *value = RCTConvertEnumValue(typeName, mapping, defaultValue, arrayElement);
result |= value.longLongValue;
}
return @(result);
}
return RCTConvertEnumValue(typeName, mapping, defaultValue, json);
}
RCT_ENUM_CONVERTER(NSLineBreakMode, (@{
@"clip": @(NSLineBreakByClipping),
@"head": @(NSLineBreakByTruncatingHead),
@"tail": @(NSLineBreakByTruncatingTail),
@"middle": @(NSLineBreakByTruncatingMiddle),
@"wordWrapping": @(NSLineBreakByWordWrapping),
}), NSLineBreakByTruncatingTail, integerValue)
RCT_ENUM_CONVERTER(NSTextAlignment, (@{
@"auto": @(NSTextAlignmentNatural),
@"left": @(NSTextAlignmentLeft),
@"center": @(NSTextAlignmentCenter),
@"right": @(NSTextAlignmentRight),
@"justify": @(NSTextAlignmentJustified),
}), NSTextAlignmentNatural, integerValue)
RCT_ENUM_CONVERTER(NSUnderlineStyle, (@{
@"solid": @(NSUnderlineStyleSingle),
@"double": @(NSUnderlineStyleDouble),
@"dotted": @(NSUnderlinePatternDot | NSUnderlineStyleSingle),
@"dashed": @(NSUnderlinePatternDash | NSUnderlineStyleSingle),
}), NSUnderlineStyleSingle, integerValue)
RCT_ENUM_CONVERTER(RCTBorderStyle, (@{
@"solid": @(RCTBorderStyleSolid),
@"dotted": @(RCTBorderStyleDotted),
@"dashed": @(RCTBorderStyleDashed),
}), RCTBorderStyleSolid, integerValue)
RCT_ENUM_CONVERTER(RCTTextDecorationLineType, (@{
@"none": @(RCTTextDecorationLineTypeNone),
@"underline": @(RCTTextDecorationLineTypeUnderline),
@"line-through": @(RCTTextDecorationLineTypeStrikethrough),
@"underline line-through": @(RCTTextDecorationLineTypeUnderlineStrikethrough),
}), RCTTextDecorationLineTypeNone, integerValue)
RCT_ENUM_CONVERTER(NSWritingDirection, (@{
@"auto": @(NSWritingDirectionNatural),
@"ltr": @(NSWritingDirectionLeftToRight),
@"rtl": @(NSWritingDirectionRightToLeft),
}), NSWritingDirectionNatural, integerValue)
RCT_ENUM_CONVERTER(UITextAutocapitalizationType, (@{
@"none": @(UITextAutocapitalizationTypeNone),
@"words": @(UITextAutocapitalizationTypeWords),
@"sentences": @(UITextAutocapitalizationTypeSentences),
@"characters": @(UITextAutocapitalizationTypeAllCharacters)
}), UITextAutocapitalizationTypeSentences, integerValue)
RCT_ENUM_CONVERTER(UITextFieldViewMode, (@{
@"never": @(UITextFieldViewModeNever),
@"while-editing": @(UITextFieldViewModeWhileEditing),
@"unless-editing": @(UITextFieldViewModeUnlessEditing),
@"always": @(UITextFieldViewModeAlways),
}), UITextFieldViewModeNever, integerValue)
RCT_ENUM_CONVERTER(UIKeyboardType, (@{
@"default": @(UIKeyboardTypeDefault),
@"ascii-capable": @(UIKeyboardTypeASCIICapable),
@"numbers-and-punctuation": @(UIKeyboardTypeNumbersAndPunctuation),
@"url": @(UIKeyboardTypeURL),
@"number-pad": @(UIKeyboardTypeNumberPad),
@"phone-pad": @(UIKeyboardTypePhonePad),
@"name-phone-pad": @(UIKeyboardTypeNamePhonePad),
@"email-address": @(UIKeyboardTypeEmailAddress),
@"decimal-pad": @(UIKeyboardTypeDecimalPad),
@"twitter": @(UIKeyboardTypeTwitter),
@"web-search": @(UIKeyboardTypeWebSearch),
// Added for Android compatibility
@"numeric": @(UIKeyboardTypeDecimalPad),
}), UIKeyboardTypeDefault, integerValue)
#if !TARGET_OS_TV
RCT_MULTI_ENUM_CONVERTER(UIDataDetectorTypes, (@{
@"phoneNumber": @(UIDataDetectorTypePhoneNumber),
@"link": @(UIDataDetectorTypeLink),
@"address": @(UIDataDetectorTypeAddress),
@"calendarEvent": @(UIDataDetectorTypeCalendarEvent),
@"none": @(UIDataDetectorTypeNone),
@"all": @(UIDataDetectorTypeAll),
}), UIDataDetectorTypePhoneNumber, unsignedLongLongValue)
#if WEBKIT_IOS_10_APIS_AVAILABLE
RCT_MULTI_ENUM_CONVERTER(WKDataDetectorTypes, (@{
@"phoneNumber": @(WKDataDetectorTypePhoneNumber),
@"link": @(WKDataDetectorTypeLink),
@"address": @(WKDataDetectorTypeAddress),
@"calendarEvent": @(WKDataDetectorTypeCalendarEvent),
@"trackingNumber": @(WKDataDetectorTypeTrackingNumber),
@"flightNumber": @(WKDataDetectorTypeFlightNumber),
@"lookupSuggestion": @(WKDataDetectorTypeLookupSuggestion),
@"none": @(WKDataDetectorTypeNone),
@"all": @(WKDataDetectorTypeAll),
}), WKDataDetectorTypePhoneNumber, unsignedLongLongValue)
#endif // WEBKIT_IOS_10_APIS_AVAILABLE
#endif // !TARGET_OS_TV
RCT_ENUM_CONVERTER(UIKeyboardAppearance, (@{
@"default": @(UIKeyboardAppearanceDefault),
@"light": @(UIKeyboardAppearanceLight),
@"dark": @(UIKeyboardAppearanceDark),
}), UIKeyboardAppearanceDefault, integerValue)
RCT_ENUM_CONVERTER(UIReturnKeyType, (@{
@"default": @(UIReturnKeyDefault),
@"go": @(UIReturnKeyGo),
@"google": @(UIReturnKeyGoogle),
@"join": @(UIReturnKeyJoin),
@"next": @(UIReturnKeyNext),
@"route": @(UIReturnKeyRoute),
@"search": @(UIReturnKeySearch),
@"send": @(UIReturnKeySend),
@"yahoo": @(UIReturnKeyYahoo),
@"done": @(UIReturnKeyDone),
@"emergency-call": @(UIReturnKeyEmergencyCall),
}), UIReturnKeyDefault, integerValue)
RCT_ENUM_CONVERTER(UIViewContentMode, (@{
@"scale-to-fill": @(UIViewContentModeScaleToFill),
@"scale-aspect-fit": @(UIViewContentModeScaleAspectFit),
@"scale-aspect-fill": @(UIViewContentModeScaleAspectFill),
@"redraw": @(UIViewContentModeRedraw),
@"center": @(UIViewContentModeCenter),
@"top": @(UIViewContentModeTop),
@"bottom": @(UIViewContentModeBottom),
@"left": @(UIViewContentModeLeft),
@"right": @(UIViewContentModeRight),
@"top-left": @(UIViewContentModeTopLeft),
@"top-right": @(UIViewContentModeTopRight),
@"bottom-left": @(UIViewContentModeBottomLeft),
@"bottom-right": @(UIViewContentModeBottomRight),
// Cross-platform values
@"cover": @(UIViewContentModeScaleAspectFill),
@"contain": @(UIViewContentModeScaleAspectFit),
@"stretch": @(UIViewContentModeScaleToFill),
}), UIViewContentModeScaleAspectFill, integerValue)
#if !TARGET_OS_TV
RCT_ENUM_CONVERTER(UIBarStyle, (@{
@"default": @(UIBarStyleDefault),
@"black": @(UIBarStyleBlack),
@"blackOpaque": @(UIBarStyleBlackOpaque),
@"blackTranslucent": @(UIBarStyleBlackTranslucent),
}), UIBarStyleDefault, integerValue)
#endif
static void convertCGStruct(const char *type, NSArray *fields, CGFloat *result, id json)
2015-04-21 16:48:29 +00:00
{
NSUInteger count = fields.count;
if ([json isKindOfClass:[NSArray class]]) {
if (RCT_DEBUG && [json count] != count) {
RCTLogError(@"Expected array with count %llu, but count is %llu: %@", (unsigned long long)count, (unsigned long long)[json count], json);
2015-04-21 16:48:29 +00:00
} else {
for (NSUInteger i = 0; i < count; i++) {
result[i] = [RCTConvert CGFloat:RCTNilIfNull(json[i])];
2015-04-21 16:48:29 +00:00
}
}
} else if ([json isKindOfClass:[NSDictionary class]]) {
for (NSUInteger i = 0; i < count; i++) {
result[i] = [RCTConvert CGFloat:RCTNilIfNull(json[fields[i]])];
2015-04-21 16:48:29 +00:00
}
} else if (json) {
RCTLogConvertError(json, @(type));
2015-04-21 16:48:29 +00:00
}
}
2015-03-30 13:07:07 +00:00
/**
* 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) \
+ (type)type:(id)json \
{ \
static NSArray *fields; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
fields = values; \
}); \
type result; \
convertCGStruct(#type, fields, (CGFloat *)&result, json); \
return result; \
2015-03-30 13:07:07 +00:00
}
RCT_CUSTOM_CONVERTER(CGFloat, CGFloat, [self double:json])
RCT_CGSTRUCT_CONVERTER(CGPoint, (@[@"x", @"y"]))
RCT_CGSTRUCT_CONVERTER(CGSize, (@[@"width", @"height"]))
RCT_CGSTRUCT_CONVERTER(CGRect, (@[@"x", @"y", @"width", @"height"]))
RCT_CGSTRUCT_CONVERTER(UIEdgeInsets, (@[@"top", @"left", @"bottom", @"right"]))
2015-03-15 23:08:42 +00:00
RCT_ENUM_CONVERTER(CGLineJoin, (@{
@"miter": @(kCGLineJoinMiter),
@"round": @(kCGLineJoinRound),
@"bevel": @(kCGLineJoinBevel),
}), kCGLineJoinMiter, intValue)
RCT_ENUM_CONVERTER(CGLineCap, (@{
@"butt": @(kCGLineCapButt),
@"round": @(kCGLineCapRound),
@"square": @(kCGLineCapSquare),
}), kCGLineCapButt, intValue)
RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
@"a", @"b", @"c", @"d", @"tx", @"ty"
]))
+ (UIColor *)UIColor:(id)json
{
if (!json) {
return nil;
}
if ([json isKindOfClass:[NSArray class]]) {
NSArray *components = [self NSNumberArray:json];
CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0;
return [UIColor colorWithRed:[self CGFloat:components[0]]
green:[self CGFloat:components[1]]
blue:[self CGFloat:components[2]]
alpha:alpha];
} else if ([json isKindOfClass:[NSNumber class]]) {
NSUInteger argb = [self NSUInteger:json];
CGFloat a = ((argb >> 24) & 0xFF) / 255.0;
CGFloat r = ((argb >> 16) & 0xFF) / 255.0;
CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
CGFloat b = (argb & 0xFF) / 255.0;
return [UIColor colorWithRed:r green:g blue:b alpha:a];
} else {
RCTLogConvertError(json, @"a UIColor. Did you forget to call processColor() on the JS side?");
return nil;
}
}
+ (CGColorRef)CGColor:(id)json
{
return [self UIColor:json].CGColor;
}
+ (YGValue)YGValue:(id)json
{
if (!json) {
return YGValueUndefined;
} else if ([json isKindOfClass:[NSNumber class]]) {
return (YGValue) { [json floatValue], YGUnitPoint };
} else if ([json isKindOfClass:[NSString class]]) {
NSString *s = (NSString *) json;
if ([s isEqualToString:@"auto"]) {
return (YGValue) { YGUndefined, YGUnitAuto };
} else if ([s hasSuffix:@"%"]) {
return (YGValue) { [[s substringToIndex:s.length] floatValue], YGUnitPercent };
} else {
RCTLogConvertError(json, @"a YGValue. Did you forget the % or pt suffix?");
}
} else {
RCTLogConvertError(json, @"a YGValue.");
}
return YGValueUndefined;
}
2015-04-21 16:48:29 +00:00
NSArray *RCTConvertArrayValue(SEL type, id json)
{
__block BOOL copy = NO;
__block NSArray *values = json = [RCTConvert NSArray:json];
[json enumerateObjectsUsingBlock:^(id jsonValue, NSUInteger idx, __unused BOOL *stop) {
2015-04-21 16:48:29 +00:00
id value = ((id(*)(Class, SEL, id))objc_msgSend)([RCTConvert class], type, jsonValue);
if (copy) {
if (value) {
[(NSMutableArray *)values addObject:value];
}
} else if (value != jsonValue) {
// Converted value is different, so we'll need to copy the array
values = [[NSMutableArray alloc] initWithCapacity:values.count];
for (NSUInteger i = 0; i < idx; i++) {
2015-04-21 16:48:29 +00:00
[(NSMutableArray *)values addObject:json[i]];
}
if (value) {
[(NSMutableArray *)values addObject:value];
}
2015-04-21 16:48:29 +00:00
copy = YES;
}
}];
return values;
}
2015-03-25 00:37:03 +00:00
RCT_ARRAY_CONVERTER(NSURL)
2015-06-23 12:17:36 +00:00
RCT_ARRAY_CONVERTER(RCTFileURL)
RCT_ARRAY_CONVERTER(UIColor)
/**
* This macro is used for creating converter functions for directly
* representable json array values that require no conversion.
*/
#if RCT_DEBUG
#define RCT_JSON_ARRAY_CONVERTER_NAMED(type, name) RCT_ARRAY_CONVERTER_NAMED(type, name)
#else
#define RCT_JSON_ARRAY_CONVERTER_NAMED(type, name) + (NSArray *)name##Array:(id)json { return json; }
#endif
#define RCT_JSON_ARRAY_CONVERTER(type) RCT_JSON_ARRAY_CONVERTER_NAMED(type, type)
RCT_JSON_ARRAY_CONVERTER(NSArray)
RCT_JSON_ARRAY_CONVERTER(NSString)
RCT_JSON_ARRAY_CONVERTER_NAMED(NSArray<NSString *>, NSStringArray)
RCT_JSON_ARRAY_CONVERTER(NSDictionary)
RCT_JSON_ARRAY_CONVERTER(NSNumber)
// Can't use RCT_ARRAY_CONVERTER due to bridged cast
+ (NSArray *)CGColorArray:(id)json
{
NSMutableArray *colors = [NSMutableArray new];
for (id value in [self NSArray:json]) {
[colors addObject:(__bridge id)[self CGColor:value]];
}
return colors;
}
2015-05-05 12:34:38 +00:00
static id RCTConvertPropertyListValue(id json)
{
if (!json || json == (id)kCFNull) {
return nil;
2015-05-06 07:11:31 +00:00
}
if ([json isKindOfClass:[NSDictionary class]]) {
2015-05-05 12:34:38 +00:00
__block BOOL copy = NO;
NSMutableDictionary *values = [[NSMutableDictionary alloc] initWithCapacity:[json count]];
[json enumerateKeysAndObjectsUsingBlock:^(NSString *key, id jsonValue, __unused BOOL *stop) {
2015-05-05 12:34:38 +00:00
id value = RCTConvertPropertyListValue(jsonValue);
if (value) {
values[key] = value;
}
copy |= value != jsonValue;
}];
return copy ? values : json;
2015-05-06 07:11:31 +00:00
}
if ([json isKindOfClass:[NSArray class]]) {
2015-05-05 12:34:38 +00:00
__block BOOL copy = NO;
__block NSArray *values = json;
[json enumerateObjectsUsingBlock:^(id jsonValue, NSUInteger idx, __unused BOOL *stop) {
2015-05-05 12:34:38 +00:00
id value = RCTConvertPropertyListValue(jsonValue);
if (copy) {
if (value) {
[(NSMutableArray *)values addObject:value];
}
} else if (value != jsonValue) {
// Converted value is different, so we'll need to copy the array
values = [[NSMutableArray alloc] initWithCapacity:values.count];
for (NSUInteger i = 0; i < idx; i++) {
2015-05-05 12:34:38 +00:00
[(NSMutableArray *)values addObject:json[i]];
}
2015-05-06 07:11:31 +00:00
if (value) {
[(NSMutableArray *)values addObject:value];
}
2015-05-05 12:34:38 +00:00
copy = YES;
}
}];
return values;
}
2015-05-06 07:11:31 +00:00
// All other JSON types are supported by property lists
return json;
2015-05-05 12:34:38 +00:00
}
+ (NSPropertyList)NSPropertyList:(id)json
{
return RCTConvertPropertyListValue(json);
}
RCT_ENUM_CONVERTER(css_backface_visibility_t, (@{
@"hidden": @NO,
@"visible": @YES
}), YES, boolValue)
RCT_ENUM_CONVERTER(YGOverflow, (@{
@"hidden": @(YGOverflowHidden),
@"visible": @(YGOverflowVisible),
@"scroll": @(YGOverflowScroll),
}), YGOverflowVisible, intValue)
RCT_ENUM_CONVERTER(YGDisplay, (@{
@"flex": @(YGDisplayFlex),
@"none": @(YGDisplayNone),
}), YGDisplayFlex, intValue)
RCT_ENUM_CONVERTER(YGFlexDirection, (@{
@"row": @(YGFlexDirectionRow),
@"row-reverse": @(YGFlexDirectionRowReverse),
@"column": @(YGFlexDirectionColumn),
@"column-reverse": @(YGFlexDirectionColumnReverse)
}), YGFlexDirectionColumn, intValue)
RCT_ENUM_CONVERTER(YGJustify, (@{
@"flex-start": @(YGJustifyFlexStart),
@"flex-end": @(YGJustifyFlexEnd),
@"center": @(YGJustifyCenter),
@"space-between": @(YGJustifySpaceBetween),
@"space-around": @(YGJustifySpaceAround),
@"space-evenly": @(YGJustifySpaceEvenly)
}), YGJustifyFlexStart, intValue)
RCT_ENUM_CONVERTER(YGAlign, (@{
@"flex-start": @(YGAlignFlexStart),
@"flex-end": @(YGAlignFlexEnd),
@"center": @(YGAlignCenter),
@"auto": @(YGAlignAuto),
@"stretch": @(YGAlignStretch),
@"baseline": @(YGAlignBaseline),
@"space-between": @(YGAlignSpaceBetween),
@"space-around": @(YGAlignSpaceAround)
}), YGAlignFlexStart, intValue)
RCT_ENUM_CONVERTER(YGDirection, (@{
@"inherit": @(YGDirectionInherit),
@"ltr": @(YGDirectionLTR),
@"rtl": @(YGDirectionRTL),
}), YGDirectionInherit, intValue)
RCT_ENUM_CONVERTER(YGPositionType, (@{
@"absolute": @(YGPositionTypeAbsolute),
@"relative": @(YGPositionTypeRelative)
}), YGPositionTypeRelative, intValue)
RCT_ENUM_CONVERTER(YGWrap, (@{
@"wrap": @(YGWrapWrap),
@"nowrap": @(YGWrapNoWrap),
@"wrap-reverse": @(YGWrapWrapReverse)
}), YGWrapNoWrap, intValue)
RCT_ENUM_CONVERTER(RCTPointerEvents, (@{
@"none": @(RCTPointerEventsNone),
@"box-only": @(RCTPointerEventsBoxOnly),
@"box-none": @(RCTPointerEventsBoxNone),
@"auto": @(RCTPointerEventsUnspecified)
}), RCTPointerEventsUnspecified, integerValue)
RCT_ENUM_CONVERTER(RCTAnimationType, (@{
@"spring": @(RCTAnimationTypeSpring),
@"linear": @(RCTAnimationTypeLinear),
@"easeIn": @(RCTAnimationTypeEaseIn),
@"easeOut": @(RCTAnimationTypeEaseOut),
@"easeInEaseOut": @(RCTAnimationTypeEaseInEaseOut),
@"keyboard": @(RCTAnimationTypeKeyboard),
}), RCTAnimationTypeEaseInEaseOut, integerValue)
@end
@interface RCTImageSource (Packager)
@property (nonatomic, assign) BOOL packagerAsset;
@end
@implementation RCTConvert (Deprecated)
/* This method is only used when loading images synchronously, e.g. for tabbar icons */
+ (UIImage *)UIImage:(id)json
{
if (!json) {
return nil;
}
RCTImageSource *imageSource = [self RCTImageSource:json];
if (!imageSource) {
return nil;
}
__block UIImage *image;
if (!RCTIsMainQueue()) {
// It seems that none of the UIImage loading methods can be guaranteed
// thread safe, so we'll pick the lesser of two evils here and block rather
// than run the risk of crashing
RCTLogWarn(@"Calling [RCTConvert UIImage:] on a background thread is not recommended");
RCTUnsafeExecuteOnMainQueueSync(^{
image = [self UIImage:json];
});
return image;
}
NSURL *URL = imageSource.request.URL;
NSString *scheme = URL.scheme.lowercaseString;
if ([scheme isEqualToString:@"file"]) {
image = RCTImageFromLocalAssetURL(URL);
if (!image) {
RCTLogConvertError(json, @"an image. File not found.");
}
} else if ([scheme isEqualToString:@"data"]) {
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
} else if ([scheme isEqualToString:@"http"] && imageSource.packagerAsset) {
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
} else {
RCTLogConvertError(json, @"an image. Only local files or data URIs are supported.");
return nil;
}
CGFloat scale = imageSource.scale;
if (!scale && imageSource.size.width) {
// If no scale provided, set scale to image width / source width
scale = CGImageGetWidth(image.CGImage) / imageSource.size.width;
}
if (scale) {
image = [UIImage imageWithCGImage:image.CGImage
scale:scale
orientation:image.imageOrientation];
}
if (!CGSizeEqualToSize(imageSource.size, CGSizeZero) &&
!CGSizeEqualToSize(imageSource.size, image.size)) {
RCTLogError(@"Image source %@ size %@ does not match loaded image size %@.",
URL.path.lastPathComponent,
NSStringFromCGSize(imageSource.size),
NSStringFromCGSize(image.size));
}
return image;
}
+ (CGImageRef)CGImage:(id)json
{
return [self UIImage:json].CGImage;
}
@end