1327 lines
43 KiB
Objective-C
1327 lines
43 KiB
Objective-C
/*
|
|
* Copyright (c) Meta Platforms, Inc. and 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>
|
|
|
|
#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) {
|
|
RCTLogInfo(@"Invalid index value %lld. Indices must be positive.", (long long)index);
|
|
}
|
|
[indexSet addIndex:index];
|
|
}
|
|
return indexSet;
|
|
}
|
|
|
|
+ (NSURL *)NSURL:(id)json
|
|
{
|
|
NSString *path = [self NSString:RCTNilIfNull(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) {
|
|
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]]) {
|
|
RCTLogInfo(
|
|
@"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) {
|
|
RCTLogInfo(@"URI must be a local file, '%@' isn't.", fileURL);
|
|
return nil;
|
|
}
|
|
if (![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]) {
|
|
RCTLogInfo(@"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) {
|
|
RCTLogInfo(
|
|
@"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;
|
|
}
|
|
|
|
+ (NSLocale *)NSLocale:(id)json
|
|
{
|
|
if ([json isKindOfClass:[NSString class]]) {
|
|
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:json];
|
|
if (!locale) {
|
|
RCTLogInfo(@"JSON String '%@' could not be interpreted as a valid locale. ", json);
|
|
}
|
|
return locale;
|
|
} else if (json) {
|
|
RCTLogConvertError(json, @"a locale");
|
|
}
|
|
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;
|
|
}
|
|
RCTLogInfo(@"Invalid %s '%@'. should be one of: %@", typeName, json, allValues);
|
|
return defaultValue;
|
|
}
|
|
if (RCT_DEBUG && ![json isKindOfClass:[NSString class]]) {
|
|
RCTLogInfo(@"Expected NSNumber or NSString for %s, received %@: %@", typeName, [json classForCoder], json);
|
|
}
|
|
id value = mapping[json];
|
|
if (RCT_DEBUG && !value && [json description].length > 0) {
|
|
RCTLogInfo(
|
|
@"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(
|
|
RCTBorderCurve,
|
|
(@{
|
|
@"circular" : @(RCTBorderCurveCircular),
|
|
@"continuous" : @(RCTBorderCurveContinuous),
|
|
}),
|
|
RCTBorderCurveCircular,
|
|
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)
|
|
|
|
+ (NSLineBreakStrategy)NSLineBreakStrategy:(id)json RCT_DYNAMIC
|
|
{
|
|
if (@available(iOS 14.0, *)) {
|
|
static NSDictionary *mapping;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
mapping = @{
|
|
@"none" : @(NSLineBreakStrategyNone),
|
|
@"standard" : @(NSLineBreakStrategyStandard),
|
|
@"hangul-word" : @(NSLineBreakStrategyHangulWordPriority),
|
|
@"push-out" : @(NSLineBreakStrategyPushOut)
|
|
};
|
|
});
|
|
return RCTConvertEnumValue("NSLineBreakStrategy", mapping, @(NSLineBreakStrategyNone), json).integerValue;
|
|
} else {
|
|
return NSLineBreakStrategyNone;
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
+ (UIKeyboardType)UIKeyboardType:(id)json RCT_DYNAMIC
|
|
{
|
|
static NSDictionary<NSString *, NSNumber *> *mapping;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
NSMutableDictionary<NSString *, NSNumber *> *temporaryMapping = [NSMutableDictionary dictionaryWithDictionary:@{
|
|
@"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),
|
|
}];
|
|
temporaryMapping[@"ascii-capable-number-pad"] = @(UIKeyboardTypeASCIICapableNumberPad);
|
|
mapping = temporaryMapping;
|
|
});
|
|
|
|
UIKeyboardType type = RCTConvertEnumValue("UIKeyboardType", mapping, @(UIKeyboardTypeDefault), json).integerValue;
|
|
return type;
|
|
}
|
|
|
|
RCT_MULTI_ENUM_CONVERTER(
|
|
UIDataDetectorTypes,
|
|
(@{
|
|
@"phoneNumber" : @(UIDataDetectorTypePhoneNumber),
|
|
@"link" : @(UIDataDetectorTypeLink),
|
|
@"address" : @(UIDataDetectorTypeAddress),
|
|
@"calendarEvent" : @(UIDataDetectorTypeCalendarEvent),
|
|
@"none" : @(UIDataDetectorTypeNone),
|
|
@"all" : @(UIDataDetectorTypeAll),
|
|
}),
|
|
UIDataDetectorTypePhoneNumber,
|
|
unsignedLongLongValue)
|
|
|
|
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)
|
|
|
|
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(
|
|
UIUserInterfaceStyle,
|
|
(@{
|
|
@"unspecified" : @(UIUserInterfaceStyleUnspecified),
|
|
@"light" : @(UIUserInterfaceStyleLight),
|
|
@"dark" : @(UIUserInterfaceStyleDark),
|
|
}),
|
|
UIUserInterfaceStyleUnspecified,
|
|
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)
|
|
|
|
RCT_ENUM_CONVERTER(
|
|
UIBarStyle,
|
|
(@{
|
|
@"default" : @(UIBarStyleDefault),
|
|
@"black" : @(UIBarStyleBlack),
|
|
@"blackOpaque" : @(UIBarStyleBlackOpaque),
|
|
@"blackTranslucent" : @(UIBarStyleBlackTranslucent),
|
|
}),
|
|
UIBarStyleDefault,
|
|
integerValue)
|
|
|
|
static void convertCGStruct(const char *type, NSArray *fields, CGFloat *result, id json)
|
|
{
|
|
NSUInteger count = fields.count;
|
|
if ([json isKindOfClass:[NSArray class]]) {
|
|
if (RCT_DEBUG && [json count] != count) {
|
|
RCTLogInfo(
|
|
@"Expected array with count %llu, but count is %llu: %@",
|
|
(unsigned long long)count,
|
|
(unsigned long long)[json count],
|
|
json);
|
|
} else {
|
|
for (NSUInteger i = 0; i < count; i++) {
|
|
result[i] = [RCTConvert CGFloat:RCTNilIfNull(json[i])];
|
|
}
|
|
}
|
|
} else if ([json isKindOfClass:[NSDictionary class]]) {
|
|
for (NSUInteger i = 0; i < count; i++) {
|
|
result[i] = [RCTConvert CGFloat:RCTNilIfNull(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) \
|
|
+(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; \
|
|
}
|
|
|
|
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" ]))
|
|
|
|
+ (UIEdgeInsets)UIEdgeInsets:(id)json
|
|
{
|
|
static NSArray *fields;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
fields = @[ @"top", @"left", @"bottom", @"right" ];
|
|
});
|
|
|
|
if ([json isKindOfClass:[NSNumber class]]) {
|
|
CGFloat value = [json doubleValue];
|
|
return UIEdgeInsetsMake(value, value, value, value);
|
|
} else {
|
|
UIEdgeInsets result;
|
|
convertCGStruct("UIEdgeInsets", fields, (CGFloat *)&result, json);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
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" ]))
|
|
|
|
static NSString *const RCTFallback = @"fallback";
|
|
static NSString *const RCTFallbackARGB = @"fallback-argb";
|
|
static NSString *const RCTSelector = @"selector";
|
|
static NSString *const RCTIndex = @"index";
|
|
|
|
/** The following dictionary defines the react-native semantic colors for ios.
|
|
* If the value for a given name is empty then the name itself
|
|
* is used as the UIColor selector.
|
|
* If the RCTSelector key is present then that value is used for a selector instead
|
|
* of the key name.
|
|
* If the given selector is not available on the running OS version then
|
|
* the RCTFallback selector is used instead.
|
|
* If the RCTIndex key is present then object returned from UIColor is an
|
|
* NSArray and the object at index RCTIndex is to be used.
|
|
*/
|
|
static NSDictionary<NSString *, NSDictionary *> *RCTSemanticColorsMap()
|
|
{
|
|
static NSDictionary<NSString *, NSDictionary *> *colorMap = nil;
|
|
if (colorMap == nil) {
|
|
NSMutableDictionary<NSString *, NSDictionary *> *map = [@{
|
|
// https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors
|
|
// Label Colors
|
|
@"labelColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB :
|
|
@(0xFF000000) // fallback for iOS<=12: RGBA returned by this semantic color in light mode on iOS 13
|
|
},
|
|
@"secondaryLabelColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0x993c3c43)
|
|
},
|
|
@"tertiaryLabelColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0x4c3c3c43)
|
|
},
|
|
@"quaternaryLabelColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0x2d3c3c43)
|
|
},
|
|
// Fill Colors
|
|
@"systemFillColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0x33787880)
|
|
},
|
|
@"secondarySystemFillColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0x28787880)
|
|
},
|
|
@"tertiarySystemFillColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0x1e767680)
|
|
},
|
|
@"quaternarySystemFillColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0x14747480)
|
|
},
|
|
// Text Colors
|
|
@"placeholderTextColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0x4c3c3c43)
|
|
},
|
|
// Standard Content Background Colors
|
|
@"systemBackgroundColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFFffffff)
|
|
},
|
|
@"secondarySystemBackgroundColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFFf2f2f7)
|
|
},
|
|
@"tertiarySystemBackgroundColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFFffffff)
|
|
},
|
|
// Grouped Content Background Colors
|
|
@"systemGroupedBackgroundColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFFf2f2f7)
|
|
},
|
|
@"secondarySystemGroupedBackgroundColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFFffffff)
|
|
},
|
|
@"tertiarySystemGroupedBackgroundColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFFf2f2f7)
|
|
},
|
|
// Separator Colors
|
|
@"separatorColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0x493c3c43)
|
|
},
|
|
@"opaqueSeparatorColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFFc6c6c8)
|
|
},
|
|
// Link Color
|
|
@"linkColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFF007aff)
|
|
},
|
|
// Nonadaptable Colors
|
|
@"darkTextColor" : @{},
|
|
@"lightTextColor" : @{},
|
|
// https://developer.apple.com/documentation/uikit/uicolor/standard_colors
|
|
// Adaptable Colors
|
|
@"systemBlueColor" : @{},
|
|
@"systemBrownColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFFa2845e)
|
|
},
|
|
@"systemGreenColor" : @{},
|
|
@"systemIndigoColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFF5856d6)
|
|
},
|
|
@"systemOrangeColor" : @{},
|
|
@"systemPinkColor" : @{},
|
|
@"systemPurpleColor" : @{},
|
|
@"systemRedColor" : @{},
|
|
@"systemTealColor" : @{},
|
|
@"systemYellowColor" : @{},
|
|
// Adaptable Gray Colors
|
|
@"systemGrayColor" : @{},
|
|
@"systemGray2Color" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFFaeaeb2)
|
|
},
|
|
@"systemGray3Color" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFFc7c7cc)
|
|
},
|
|
@"systemGray4Color" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFFd1d1d6)
|
|
},
|
|
@"systemGray5Color" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFFe5e5ea)
|
|
},
|
|
@"systemGray6Color" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0xFFf2f2f7)
|
|
},
|
|
// Transparent Color
|
|
@"clearColor" : @{
|
|
// iOS 13.0
|
|
RCTFallbackARGB : @(0x00000000)
|
|
},
|
|
} mutableCopy];
|
|
// The color names are the Objective-C UIColor selector names,
|
|
// but Swift selector names are valid as well, so make aliases.
|
|
static NSString *const RCTColorSuffix = @"Color";
|
|
NSMutableDictionary<NSString *, NSDictionary *> *aliases = [NSMutableDictionary new];
|
|
for (NSString *objcSelector in map) {
|
|
RCTAssert(
|
|
[objcSelector hasSuffix:RCTColorSuffix], @"A selector in the color map did not end with the suffix Color.");
|
|
NSMutableDictionary *entry = [map[objcSelector] mutableCopy];
|
|
RCTAssert([entry objectForKey:RCTSelector] == nil, @"Entry should not already have an RCTSelector");
|
|
NSString *swiftSelector = [objcSelector substringToIndex:[objcSelector length] - [RCTColorSuffix length]];
|
|
entry[RCTSelector] = objcSelector;
|
|
aliases[swiftSelector] = entry;
|
|
}
|
|
[map addEntriesFromDictionary:aliases];
|
|
#if DEBUG
|
|
[map addEntriesFromDictionary:@{
|
|
// The follow exist for Unit Tests
|
|
@"unitTestFallbackColor" : @{RCTFallback : @"gridColor"},
|
|
@"unitTestFallbackColorIOS" : @{RCTFallback : @"blueColor"},
|
|
@"unitTestFallbackColorEven" : @{
|
|
RCTSelector : @"unitTestFallbackColorEven",
|
|
RCTIndex : @0,
|
|
RCTFallback : @"controlAlternatingRowBackgroundColors"
|
|
},
|
|
@"unitTestFallbackColorOdd" : @{
|
|
RCTSelector : @"unitTestFallbackColorOdd",
|
|
RCTIndex : @1,
|
|
RCTFallback : @"controlAlternatingRowBackgroundColors"
|
|
},
|
|
}];
|
|
#endif
|
|
colorMap = [map copy];
|
|
}
|
|
|
|
return colorMap;
|
|
}
|
|
|
|
/** Returns a UIColor based on a semantic color name.
|
|
* Returns nil if the semantic color name is invalid.
|
|
*/
|
|
static UIColor *RCTColorFromSemanticColorName(NSString *semanticColorName)
|
|
{
|
|
NSDictionary<NSString *, NSDictionary *> *colorMap = RCTSemanticColorsMap();
|
|
UIColor *color = nil;
|
|
NSDictionary<NSString *, id> *colorInfo = colorMap[semanticColorName];
|
|
if (colorInfo) {
|
|
NSString *semanticColorSelector = colorInfo[RCTSelector];
|
|
if (semanticColorSelector == nil) {
|
|
semanticColorSelector = semanticColorName;
|
|
}
|
|
SEL selector = NSSelectorFromString(semanticColorSelector);
|
|
if (![UIColor respondsToSelector:selector]) {
|
|
NSNumber *fallbackRGB = colorInfo[RCTFallbackARGB];
|
|
if (fallbackRGB != nil) {
|
|
RCTAssert([fallbackRGB isKindOfClass:[NSNumber class]], @"fallback ARGB is not a number");
|
|
return [RCTConvert UIColor:fallbackRGB];
|
|
}
|
|
semanticColorSelector = colorInfo[RCTFallback];
|
|
selector = NSSelectorFromString(semanticColorSelector);
|
|
}
|
|
RCTAssert([UIColor respondsToSelector:selector], @"RCTUIColor does not respond to a semantic color selector.");
|
|
Class klass = [UIColor class];
|
|
IMP imp = [klass methodForSelector:selector];
|
|
id (*getSemanticColorObject)(id, SEL) = (void *)imp;
|
|
id colorObject = getSemanticColorObject(klass, selector);
|
|
if ([colorObject isKindOfClass:[UIColor class]]) {
|
|
color = colorObject;
|
|
} else if ([colorObject isKindOfClass:[NSArray class]]) {
|
|
NSArray *colors = colorObject;
|
|
NSNumber *index = colorInfo[RCTIndex];
|
|
RCTAssert(index, @"index should not be null");
|
|
color = colors[[index unsignedIntegerValue]];
|
|
} else {
|
|
RCTAssert(false, @"selector return an unknown object type");
|
|
}
|
|
}
|
|
return color;
|
|
}
|
|
|
|
/** Returns an alphabetically sorted comma separated list of the valid semantic color names
|
|
*/
|
|
static NSString *RCTSemanticColorNames()
|
|
{
|
|
NSMutableString *names = [NSMutableString new];
|
|
NSDictionary<NSString *, NSDictionary *> *colorMap = RCTSemanticColorsMap();
|
|
NSArray *allKeys =
|
|
[[[colorMap allKeys] mutableCopy] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
|
|
|
|
for (id key in allKeys) {
|
|
if ([names length]) {
|
|
[names appendString:@", "];
|
|
}
|
|
[names appendString:key];
|
|
}
|
|
return names;
|
|
}
|
|
|
|
+ (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 if ([json isKindOfClass:[NSDictionary class]]) {
|
|
NSDictionary *dictionary = json;
|
|
id value = nil;
|
|
if ((value = [dictionary objectForKey:@"semantic"])) {
|
|
if ([value isKindOfClass:[NSString class]]) {
|
|
NSString *semanticName = value;
|
|
UIColor *color = [UIColor colorNamed:semanticName];
|
|
if (color != nil) {
|
|
return color;
|
|
}
|
|
color = RCTColorFromSemanticColorName(semanticName);
|
|
if (color == nil) {
|
|
RCTLogConvertError(
|
|
json,
|
|
[@"a UIColor. Expected one of the following values: " stringByAppendingString:RCTSemanticColorNames()]);
|
|
}
|
|
return color;
|
|
} else if ([value isKindOfClass:[NSArray class]]) {
|
|
for (id name in value) {
|
|
UIColor *color = [UIColor colorNamed:name];
|
|
if (color != nil) {
|
|
return color;
|
|
}
|
|
color = RCTColorFromSemanticColorName(name);
|
|
if (color != nil) {
|
|
return color;
|
|
}
|
|
}
|
|
RCTLogConvertError(
|
|
json,
|
|
[@"a UIColor. None of the names in the array were one of the following values: "
|
|
stringByAppendingString:RCTSemanticColorNames()]);
|
|
return nil;
|
|
}
|
|
RCTLogConvertError(
|
|
json, @"a UIColor. Expected either a single name or an array of names but got something else.");
|
|
return nil;
|
|
} else if ((value = [dictionary objectForKey:@"dynamic"])) {
|
|
NSDictionary *appearances = value;
|
|
id light = [appearances objectForKey:@"light"];
|
|
UIColor *lightColor = [RCTConvert UIColor:light];
|
|
id dark = [appearances objectForKey:@"dark"];
|
|
UIColor *darkColor = [RCTConvert UIColor:dark];
|
|
id highContrastLight = [appearances objectForKey:@"highContrastLight"];
|
|
UIColor *highContrastLightColor = [RCTConvert UIColor:highContrastLight];
|
|
id highContrastDark = [appearances objectForKey:@"highContrastDark"];
|
|
UIColor *highContrastDarkColor = [RCTConvert UIColor:highContrastDark];
|
|
if (lightColor != nil && darkColor != nil) {
|
|
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
|
if (@available(iOS 13.0, *)) {
|
|
UIColor *color = [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
|
|
UITraitCollection *_Nonnull collection) {
|
|
if (collection.userInterfaceStyle == UIUserInterfaceStyleDark) {
|
|
if (collection.accessibilityContrast == UIAccessibilityContrastHigh && highContrastDarkColor != nil) {
|
|
return highContrastDarkColor;
|
|
} else {
|
|
return darkColor;
|
|
}
|
|
} else {
|
|
if (collection.accessibilityContrast == UIAccessibilityContrastHigh && highContrastLightColor != nil) {
|
|
return highContrastLightColor;
|
|
} else {
|
|
return lightColor;
|
|
}
|
|
}
|
|
}];
|
|
return color;
|
|
} else {
|
|
#endif
|
|
return lightColor;
|
|
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
|
}
|
|
#endif
|
|
} else {
|
|
RCTLogConvertError(json, @"a UIColor. Expected an iOS dynamic appearance aware color.");
|
|
return nil;
|
|
}
|
|
} else {
|
|
RCTLogConvertError(json, @"a UIColor. Expected an iOS semantic color or dynamic appearance aware color.");
|
|
return nil;
|
|
}
|
|
} 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:@"%"]) {
|
|
float floatValue;
|
|
if ([[NSScanner scannerWithString:s] scanFloat:&floatValue]) {
|
|
return (YGValue){floatValue, YGUnitPercent};
|
|
}
|
|
} else {
|
|
RCTLogAdvice(
|
|
@"\"%@\" is not a valid dimension. Dimensions must be a number, \"auto\", or a string suffixed with \"%%\".",
|
|
s);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
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),
|
|
@"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,
|
|
(@{
|
|
@"static" : @(YGPositionTypeStatic),
|
|
@"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);
|
|
// There is a case where this may fail when the image is at the bundle location.
|
|
// RCTImageFromLocalAssetURL only checks for the image in the same location as the jsbundle
|
|
// Hence, if the bundle is CodePush-ed, it will not be able to find the image.
|
|
// This check is added here instead of being inside RCTImageFromLocalAssetURL, since
|
|
// we don't want breaking changes to RCTImageFromLocalAssetURL, which is called in a lot of places
|
|
// This is a deprecated method, and hence has the least impact on existing code. Basically,
|
|
// instead of crashing the app, it tries one more location for the image.
|
|
if (!image) {
|
|
image = RCTImageFromLocalBundleAssetURL(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)) {
|
|
RCTLogInfo(
|
|
@"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
|