react-native/React/Base/RCTConvert.m

790 lines
26 KiB
Objective-C

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTConvert.h"
#import <objc/message.h>
#import <CoreText/CoreText.h>
#import "RCTDefines.h"
#import "RCTImageSource.h"
#import "RCTParserUtils.h"
#import "RCTUtils.h"
@implementation RCTConvert
RCT_CONVERTER(id, id, self)
RCT_CONVERTER(BOOL, BOOL, boolValue)
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);
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; \
}
#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])
+ (NSIndexSet *)NSIndexSet:(id)json
{
json = [self NSNumberArray:json];
NSMutableIndexSet *indexSet = [NSMutableIndexSet new];
for (NSNumber *number in json) {
NSInteger index = number.integerValue;
if (RCT_DEBUG && index < 0) {
RCTLogError(@"Invalid index value %zd. Indices must be positive.", index);
}
[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
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) {
path = [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
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;
}
+ (RCTFileURL *)RCTFileURL:(id)json
{
NSURL *fileURL = [self NSURL:json];
if (!fileURL.fileURL) {
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;
}
+ (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];
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);
}
return date;
} else if (json) {
RCTLogConvertError(json, @"a date");
}
return nil;
}
// JS Standard for time is milliseconds
RCT_CUSTOM_CONVERTER(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0)
// JS standard for time zones is minutes.
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)
#endif
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),
}), UIBarStyleDefault, integerValue)
#endif
// TODO: normalise the use of w/width so we can do away with the alias values (#6566645)
static void RCTConvertCGStructValue(const char *type, NSArray *fields, NSDictionary *aliases, CGFloat *result, id json)
{
NSUInteger count = fields.count;
if ([json isKindOfClass:[NSArray class]]) {
if (RCT_DEBUG && [json count] != count) {
RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json);
} else {
for (NSUInteger i = 0; i < count; i++) {
result[i] = [RCTConvert CGFloat:json[i]];
}
}
} else if ([json isKindOfClass:[NSDictionary class]]) {
if (aliases.count) {
json = [json mutableCopy];
for (NSString *alias in aliases) {
NSString *key = aliases[alias];
NSNumber *number = json[alias];
if (number != nil) {
RCTLogWarn(@"Using deprecated '%@' property for '%s'. Use '%@' instead.", alias, type, key);
((NSMutableDictionary *)json)[key] = number;
}
}
}
for (NSUInteger i = 0; i < count; i++) {
result[i] = [RCTConvert CGFloat:json[fields[i]]];
}
} else if (json) {
RCTLogConvertError(json, @(type));
}
}
/**
* 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, aliases) \
+ (type)type:(id)json \
{ \
static NSArray *fields; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
fields = values; \
}); \
type result; \
RCTConvertCGStructValue(#type, fields, aliases, (CGFloat *)&result, json); \
return result; \
}
RCT_CUSTOM_CONVERTER(CGFloat, CGFloat, [self double:json])
RCT_CGSTRUCT_CONVERTER(CGPoint, (@[@"x", @"y"]), (@{@"l": @"x", @"t": @"y"}))
RCT_CGSTRUCT_CONVERTER(CGSize, (@[@"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
RCT_CGSTRUCT_CONVERTER(CGRect, (@[@"x", @"y", @"width", @"height"]), (@{@"l": @"x", @"t": @"y", @"w": @"width", @"h": @"height"}))
RCT_CGSTRUCT_CONVERTER(UIEdgeInsets, (@[@"top", @"left", @"bottom", @"right"]), nil)
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"
]), nil)
+ (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;
}
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) {
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++) {
[(NSMutableArray *)values addObject:json[i]];
}
if (value) {
[(NSMutableArray *)values addObject:value];
}
copy = YES;
}
}];
return values;
}
SEL RCTConvertSelectorForType(NSString *type)
{
const char *input = type.UTF8String;
return NSSelectorFromString([RCTParseType(&input) stringByAppendingString:@":"]);
}
RCT_ARRAY_CONVERTER(NSURL)
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;
}
static id RCTConvertPropertyListValue(id json)
{
if (!json || json == (id)kCFNull) {
return nil;
}
if ([json isKindOfClass:[NSDictionary class]]) {
__block BOOL copy = NO;
NSMutableDictionary *values = [[NSMutableDictionary alloc] initWithCapacity:[json count]];
[json enumerateKeysAndObjectsUsingBlock:^(NSString *key, id jsonValue, __unused BOOL *stop) {
id value = RCTConvertPropertyListValue(jsonValue);
if (value) {
values[key] = value;
}
copy |= value != jsonValue;
}];
return copy ? values : json;
}
if ([json isKindOfClass:[NSArray class]]) {
__block BOOL copy = NO;
__block NSArray *values = json;
[json enumerateObjectsUsingBlock:^(id jsonValue, NSUInteger idx, __unused BOOL *stop) {
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++) {
[(NSMutableArray *)values addObject:json[i]];
}
if (value) {
[(NSMutableArray *)values addObject:value];
}
copy = YES;
}
}];
return values;
}
// All other JSON types are supported by property lists
return json;
}
+ (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)
}), 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)
}), 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