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

1263 строки
44 KiB
Objective-C

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTConvert.h"
#import <objc/message.h>
#import <CoreText/CoreText.h>
#import "RCTDefines.h"
#import "RCTImageSource.h"
#import "RCTParserUtils.h"
#import "RCTUtils.h"
#if TARGET_OS_OSX // [TODO(macOS ISS#2323203)
#import "RCTDynamicColor.h"
#endif // ]TODO(macOS ISS#2323203)
@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: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(RCTTextDecorationLineType, (@{
@"none": @(RCTTextDecorationLineTypeNone),
@"underline": @(RCTTextDecorationLineTypeUnderline),
@"line-through": @(RCTTextDecorationLineTypeStrikethrough),
@"underline line-through": @(RCTTextDecorationLineTypeUnderlineStrikethrough),
}), RCTTextDecorationLineTypeNone, integerValue)
// [TODO(OSS Candidate ISS#2710739)
RCT_ENUM_CONVERTER(RCTFontSmoothing, (@{
@"auto": @(RCTFontSmoothingAuto),
@"none": @(RCTFontSmoothingNone),
@"antialiased": @(RCTFontSmoothingAntialiased),
@"subpixel-antialiased": @(RCTFontSmoothingSubpixelAntialiased),
}), RCTFontSmoothingAuto, integerValue)
// ]TODO(OSS Candidate ISS#2710739)
RCT_ENUM_CONVERTER(NSWritingDirection, (@{
@"auto": @(NSWritingDirectionNatural),
@"ltr": @(NSWritingDirectionLeftToRight),
@"rtl": @(NSWritingDirectionRightToLeft),
}), NSWritingDirectionNatural, integerValue)
#if !TARGET_OS_OSX // TODO(macOS ISS#2323203)
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),
@"ascii-capable-number-pad": @(UIKeyboardTypeASCIICapableNumberPad),
// Added for Android compatibility
@"numeric": @(UIKeyboardTypeDecimalPad),
}), UIKeyboardTypeDefault, integerValue)
#if !TARGET_OS_TV
RCT_MULTI_ENUM_CONVERTER(UIDataDetectorTypes, (@{
@"phoneNumber": @(UIDataDetectorTypePhoneNumber),
@"link": @(UIDataDetectorTypeLink),
@"address": @(UIDataDetectorTypeAddress),
@"calendarEvent": @(UIDataDetectorTypeCalendarEvent),
@"none": @(UIDataDetectorTypeNone),
@"all": @(UIDataDetectorTypeAll),
}), UIDataDetectorTypePhoneNumber, unsignedLongLongValue)
#if WEBKIT_IOS_10_APIS_AVAILABLE
RCT_MULTI_ENUM_CONVERTER(WKDataDetectorTypes, (@{
@"phoneNumber": @(WKDataDetectorTypePhoneNumber),
@"link": @(WKDataDetectorTypeLink),
@"address": @(WKDataDetectorTypeAddress),
@"calendarEvent": @(WKDataDetectorTypeCalendarEvent),
@"trackingNumber": @(WKDataDetectorTypeTrackingNumber),
@"flightNumber": @(WKDataDetectorTypeFlightNumber),
@"lookupSuggestion": @(WKDataDetectorTypeLookupSuggestion),
@"none": @(WKDataDetectorTypeNone),
@"all": @(WKDataDetectorTypeAll),
}), WKDataDetectorTypePhoneNumber, unsignedLongLongValue)
#endif // WEBKIT_IOS_10_APIS_AVAILABLE
#endif // !TARGET_OS_TV
RCT_ENUM_CONVERTER(UIKeyboardAppearance, (@{
@"default": @(UIKeyboardAppearanceDefault),
@"light": @(UIKeyboardAppearanceLight),
@"dark": @(UIKeyboardAppearanceDark),
}), UIKeyboardAppearanceDefault, integerValue)
RCT_ENUM_CONVERTER(UIReturnKeyType, (@{
@"default": @(UIReturnKeyDefault),
@"go": @(UIReturnKeyGo),
@"google": @(UIReturnKeyGoogle),
@"join": @(UIReturnKeyJoin),
@"next": @(UIReturnKeyNext),
@"route": @(UIReturnKeyRoute),
@"search": @(UIReturnKeySearch),
@"send": @(UIReturnKeySend),
@"yahoo": @(UIReturnKeyYahoo),
@"done": @(UIReturnKeyDone),
@"emergency-call": @(UIReturnKeyEmergencyCall),
}), UIReturnKeyDefault, integerValue)
RCT_ENUM_CONVERTER(UIViewContentMode, (@{
@"scale-to-fill": @(UIViewContentModeScaleToFill),
@"scale-aspect-fit": @(UIViewContentModeScaleAspectFit),
@"scale-aspect-fill": @(UIViewContentModeScaleAspectFill),
@"redraw": @(UIViewContentModeRedraw),
@"center": @(UIViewContentModeCenter),
@"top": @(UIViewContentModeTop),
@"bottom": @(UIViewContentModeBottom),
@"left": @(UIViewContentModeLeft),
@"right": @(UIViewContentModeRight),
@"top-left": @(UIViewContentModeTopLeft),
@"top-right": @(UIViewContentModeTopRight),
@"bottom-left": @(UIViewContentModeBottomLeft),
@"bottom-right": @(UIViewContentModeBottomRight),
// Cross-platform values
@"cover": @(UIViewContentModeScaleAspectFill),
@"contain": @(UIViewContentModeScaleAspectFit),
@"stretch": @(UIViewContentModeScaleToFill),
}), UIViewContentModeScaleAspectFill, integerValue)
#if !TARGET_OS_TV
RCT_ENUM_CONVERTER(UIBarStyle, (@{
@"default": @(UIBarStyleDefault),
@"black": @(UIBarStyleBlack),
@"blackOpaque": @(UIBarStyleBlackOpaque),
@"blackTranslucent": @(UIBarStyleBlackTranslucent),
}), UIBarStyleDefault, integerValue)
#endif
#else // [TODO(macOS ISS#2323203)
RCT_MULTI_ENUM_CONVERTER(NSTextCheckingTypes, (@{
@"ortography": @(NSTextCheckingTypeOrthography),
@"spelling": @(NSTextCheckingTypeSpelling),
@"grammar": @(NSTextCheckingTypeGrammar),
@"calendarEvent": @(NSTextCheckingTypeDate),
@"address": @(NSTextCheckingTypeAddress),
@"link": @(NSTextCheckingTypeLink),
@"quote": @(NSTextCheckingTypeQuote),
@"dash": @(NSTextCheckingTypeDash),
@"replacement": @(NSTextCheckingTypeReplacement),
@"correction": @(NSTextCheckingTypeCorrection),
@"regularExpression": @(NSTextCheckingTypeRegularExpression),
@"phoneNumber": @(NSTextCheckingTypePhoneNumber),
@"transitInformation": @(NSTextCheckingTypeTransitInformation),
}), NSTextCheckingTypeOrthography, unsignedLongLongValue)
#endif // ]TODO(macOS ISS#2323203)
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"]))
RCT_CGSTRUCT_CONVERTER(UIEdgeInsets, (@[@"top", @"left", @"bottom", @"right"]))
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"
]))
// [TODO(macOS ISS#2323203)
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 macos and 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) {
colorMap = @{
#if TARGET_OS_OSX
// https://developer.apple.com/documentation/appkit/nscolor/ui_element_colors
// Label Colors
@"labelColor": @{}, // 10_10
@"secondaryLabelColor": @{}, // 10_10
@"tertiaryLabelColor": @{}, // 10_10
@"quaternaryLabelColor": @{}, // 10_10
// Text Colors
@"textColor": @{},
@"placeholderTextColor": @{}, // 10_10
@"selectedTextColor": @{},
@"textBackgroundColor": @{},
@"selectedTextBackgroundColor": @{},
@"keyboardFocusIndicatorColor": @{},
@"unemphasizedSelectedTextColor": @{ // 10_14
RCTFallback: @"selectedTextColor"
},
@"unemphasizedSelectedTextBackgroundColor": @{ // 10_14
RCTFallback: @"textBackgroundColor"
},
// Content Colors
@"linkColor": @{}, // 10_10
@"separatorColor": @{ // 10_14: Replacement for +controlHighlightColor, +controlLightHighlightColor, +controlShadowColor, +controlDarkShadowColor
RCTFallback: @"gridColor"
},
@"selectedContentBackgroundColor": @{ // 10_14: Alias for +alternateSelectedControlColor
RCTFallback: @"alternateSelectedControlColor"
},
@"unemphasizedSelectedContentBackgroundColor": @{ // 10_14: Alias for +secondarySelectedControlColor
RCTFallback: @"secondarySelectedControlColor"
},
// Menu Colors
@"selectedMenuItemTextColor": @{},
// Table Colors
@"gridColor": @{},
@"headerTextColor": @{},
@"alternatingContentBackgroundColorEven": @{ // 10_14: Alias for +controlAlternatingRowBackgroundColors
RCTSelector: @"alternatingContentBackgroundColors",
RCTIndex: @0,
RCTFallback: @"controlAlternatingRowBackgroundColors"
},
@"alternatingContentBackgroundColorOdd": @{ // 10_14: Alias for +controlAlternatingRowBackgroundColors
RCTSelector: @"alternatingContentBackgroundColors",
RCTIndex: @1,
RCTFallback: @"controlAlternatingRowBackgroundColors"
},
// Control Colors
@"controlAccentColor": @{ // 10_14
RCTFallback: @"controlColor"
},
@"controlColor": @{},
@"controlBackgroundColor": @{},
@"controlTextColor": @{},
@"disabledControlTextColor": @{},
@"selectedControlColor": @{},
@"selectedControlTextColor": @{},
@"alternateSelectedControlTextColor": @{},
@"scrubberTexturedBackgroundColor": @{}, // 10_12_2
// Window Colors
@"windowBackgroundColor": @{},
@"windowFrameTextColor": @{},
@"underPageBackgroundColor": @{}, // 10_8
// Highlights and Shadows
@"findHighlightColor": @{ // 10_13
RCTFallback: @"highlightColor"
},
@"highlightColor": @{},
@"shadowColor": @{},
// https://developer.apple.com/documentation/appkit/nscolor/standard_colors
// Standard Colors
@"systemBlueColor": @{}, // 10_10
@"systemBrownColor": @{}, // 10_10
@"systemGrayColor": @{}, // 10_10
@"systemGreenColor": @{}, // 10_10
@"systemOrangeColor": @{}, // 10_10
@"systemPinkColor": @{}, // 10_10
@"systemPurpleColor": @{}, // 10_10
@"systemRedColor": @{}, // 10_10
@"systemYellowColor": @{}, // 10_10
#else // TARGET_OS_IOS
// 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)
},
#endif
#if DEBUG
// 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
};
}
return colorMap;
}
/** Returns a UIColor based on a semantic color name.
* Returns nil if the semantic color name is invalid.
*/
static RCTUIColor *RCTColorFromSemanticColorName(NSString *semanticColorName)
{
NSDictionary<NSString *, NSDictionary *> *colorMap = RCTSemanticColorsMap();
RCTUIColor *color = nil;
NSDictionary<NSString *, id> *colorInfo = colorMap[semanticColorName];
if (colorInfo) {
NSString *semanticColorSelector = colorInfo[RCTSelector];
if (semanticColorSelector == nil) {
semanticColorSelector = semanticColorName;
}
SEL selector = NSSelectorFromString(semanticColorSelector);
if (![RCTUIColor 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 ([RCTUIColor respondsToSelector:selector], @"RCTUIColor does not respond to a semantic color selector.");
Class klass = [RCTUIColor class];
IMP imp = [klass methodForSelector:selector];
id (*getSemanticColorObject)(id, SEL) = (void *)imp;
id colorObject = getSemanticColorObject(klass, selector);
if ([colorObject isKindOfClass:[RCTUIColor 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 a comma seperated list of the valid semantic color names
*/
static NSString *RCTSemanticColorNames()
{
NSMutableString *names = [[NSMutableString alloc] init];
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;
}
+ (RCTUIColor *)NSColor:(id)json
{
return [RCTConvert UIColor:json];
}
// ]TODO(macOS ISS#2323203)
+ (RCTUIColor *)UIColor:(id)json // TODO(OSS Candidate ISS#2710739)
{
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 [RCTUIColor colorWithRed:[self CGFloat:components[0]] // TODO(OSS Candidate ISS#2710739)
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 [RCTUIColor colorWithRed:r green:g blue:b alpha:a]; // TODO(OSS Candidate ISS#2710739)
// [TODO(macOS ISS#2323203)
} else if ([json isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = json;
id value = nil;
if ((value = [dictionary objectForKey:@"semantic"])) {
NSString *semanticName = value;
RCTUIColor *color = RCTColorFromSemanticColorName(semanticName);
if (color == nil) {
RCTLogConvertError(json, [@"a UIColor. Expected one of the following values: " stringByAppendingString:RCTSemanticColorNames()]);
}
return color;
} else if ((value = [dictionary objectForKey:@"dynamic"])) {
NSDictionary *appearances = value;
id light = [appearances objectForKey:@"light"];
RCTUIColor *lightColor = [RCTConvert UIColor:light];
id dark = [appearances objectForKey:@"dark"];
RCTUIColor *darkColor = [RCTConvert UIColor:dark];
if (lightColor != nil && darkColor != nil) {
#if TARGET_OS_OSX
RCTDynamicColor *color = [[RCTDynamicColor alloc] initWithAquaColor:lightColor darkAquaColor:darkColor];
return color;
#else
#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) {
return collection.userInterfaceStyle == UIUserInterfaceStyleLight ? lightColor : darkColor;
}];
return color;
} else {
#endif
return lightColor;
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
}
#endif
#endif
} else {
RCTLogConvertError(json, @"a UIColor. Expected a mac dynamic appearance aware color.");
return nil;
}
} else {
RCTLogConvertError(json, @"a UIColor. Expected a mac semantic color or dynamic appearance aware color.");
return nil;
}
// ]TODO(macOS ISS#2323203)
} 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(RCTUIColor) // TODO(OSS Candidate ISS#2710739)
/**
* 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;
}
#if TARGET_OS_OSX // [TODO(macOS ISS#2323203)
+ (NSArray<NSPasteboardType> *)NSPasteboardType:(id)json
{
NSString *type = [self NSString:json];
if (!type) {
return @[];
}
if ([type isEqualToString:@"fileUrl"]) {
return @[NSFilenamesPboardType];
}
return @[];
}
+ (NSArray<NSPasteboardType> *)NSPasteboardTypeArray:(id)json
{
if ([json isKindOfClass:[NSString class]]) {
return [RCTConvert NSPasteboardType:json];
} else if ([json isKindOfClass:[NSArray class]]) {
NSMutableArray *mutablePastboardTypes = [[NSMutableArray alloc] init];
for (NSString *type in json) {
[mutablePastboardTypes addObjectsFromArray:[RCTConvert NSPasteboardType:type]];
return mutablePastboardTypes.copy;
}
}
return @[];
}
#endif // ]TODO(macOS ISS#2323203)
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, (@{
@"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, (@{
#if !TARGET_OS_OSX // TODO(macOS ISS#2323203)
@"spring": @(RCTAnimationTypeSpring),
#endif // TODO(macOS ISS#2323203)
@"linear": @(RCTAnimationTypeLinear),
@"easeIn": @(RCTAnimationTypeEaseIn),
@"easeOut": @(RCTAnimationTypeEaseOut),
@"easeInEaseOut": @(RCTAnimationTypeEaseInEaseOut),
#if !TARGET_OS_OSX // TODO(macOS ISS#2323203)
@"keyboard": @(RCTAnimationTypeKeyboard),
#endif // TODO(macOS ISS#2323203)
}), RCTAnimationTypeEaseInEaseOut, integerValue)
#if TARGET_OS_OSX // [TODO(macOS ISS#2323203)
+ (NSString*)accessibilityRoleFromTrait:(NSString*)trait
{
// a subset of iOS accessibilityTraits map to macOS accessiblityRoles:
if ([trait isEqualToString:@"button"]) {
return NSAccessibilityButtonRole;
} else if ([trait isEqualToString:@"text"]) {
return NSAccessibilityStaticTextRole;
} else if ([trait isEqualToString:@"link"]) {
return NSAccessibilityLinkRole;
} else if ([trait isEqualToString:@"image"]) {
return NSAccessibilityImageRole;
// a set of RN accessibilityTraits are macOS specific accessiblity roles:
} else if ([trait isEqualToString:@"group"]) {
return NSAccessibilityGroupRole;
} else if ([trait isEqualToString:@"list"]) {
return NSAccessibilityListRole;
}
return NSAccessibilityUnknownRole;
}
+ (NSString *)accessibilityRoleFromTraits:(id)json
{
if ([json isKindOfClass:[NSString class]]) {
return [RCTConvert accessibilityRoleFromTrait:json];
} else if ([json isKindOfClass:[NSArray class]]) {
for (NSString *trait in json) {
NSString *accessibilityRole = [RCTConvert accessibilityRoleFromTrait:trait];
if (![accessibilityRole isEqualToString:NSAccessibilityUnknownRole]) {
return accessibilityRole;
}
}
}
return NSAccessibilityUnknownRole;
}
#endif // ]TODO(macOS ISS#2323203)
@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 = UIImageWithData([NSData dataWithContentsOfURL:URL]); // TODO(macOS ISS#2323203)
} else if ([scheme isEqualToString:@"http"] && imageSource.packagerAsset) {
image = UIImageWithData([NSData dataWithContentsOfURL:URL]); // TODO(macOS ISS#2323203)
} else {
RCTLogConvertError(json, @"an image. Only local files or data URIs are supported.");
return nil;
}
CGImageRef imageRef = UIImageGetCGImageRef(image); // TODO(macOS ISS#2323203)
#if !TARGET_OS_OSX // TODO(macOS ISS#2323203)
CGFloat scale = imageSource.scale;
if (!scale && imageSource.size.width) {
// If no scale provided, set scale to image width / source width
scale = CGImageGetWidth(imageRef) / imageSource.size.width; // TODO(macOS ISS#2323203)
}
if (scale) {
image = [UIImage imageWithCGImage:imageRef
scale:scale
orientation:image.imageOrientation];
}
#else // [TODO(macOS ISS#2323203)
if (!CGSizeEqualToSize(image.size, imageSource.size)) {
image = [[NSImage alloc] initWithCGImage:imageRef size:imageSource.size];
}
#endif // ]TODO(macOS ISS#2323203)
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 UIImageGetCGImageRef([self UIImage:json]); // TODO(macOS ISS#2323203)
}
@end