react-native-macos/React/Base/RCTConvert.m

1314 строки
42 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) {
RCTLogError(@"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]]) {
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;
}
+ (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;
}
// 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(
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(
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) {
RCTLogError(
@"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:@"%"]) {
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;
}
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)) {
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