From 71d257709b9326a8e1e9b699d2b56d7464603498 Mon Sep 17 00:00:00 2001 From: Wojciech Lewicki Date: Mon, 25 Nov 2024 13:40:36 +0100 Subject: [PATCH 1/2] fix: use dynamics to cover string and number --- ios/RNCPicker.mm | 5 +- ios/RNCPickerComponentView.mm | 5 +- ios/RNCPickerFabricConversions.h | 106 +++++++++++++++++++++++++++++++ js/RNCPickerNativeComponent.js | 6 +- 4 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 ios/RNCPickerFabricConversions.h diff --git a/ios/RNCPicker.mm b/ios/RNCPicker.mm index 4e602987d..f21b6c5dc 100644 --- a/ios/RNCPicker.mm +++ b/ios/RNCPicker.mm @@ -6,6 +6,9 @@ */ #import "RNCPicker.h" +#ifdef RCT_NEW_ARCH_ENABLED +#import "RNCPickerFabricConversions.h" +#endif #import #import @@ -170,7 +173,7 @@ - (void)pickerView:(__unused UIPickerView *)pickerView std::dynamic_pointer_cast(eventEmitter) ->onChange(facebook::react::RNCPickerEventEmitter::OnChange{ .newIndex = (int)row, - .newValue = RCTStringFromNSString(_items[row][@"value"]), + .newValue = RNCPickerRNCPickerConvertIdToFollyDynamic(_items[row][@"value"]), }); } } diff --git a/ios/RNCPickerComponentView.mm b/ios/RNCPickerComponentView.mm index 043070333..75edfa986 100644 --- a/ios/RNCPickerComponentView.mm +++ b/ios/RNCPickerComponentView.mm @@ -2,6 +2,7 @@ #import "RNCPickerComponentView.h" #import "RNCPicker.h" +#import "RNCPickerFabricConversions.h" #import #import @@ -54,8 +55,8 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & for (RNCPickerItemsStruct item : newProps.items) { NSMutableDictionary *dictItem = [NSMutableDictionary new]; - dictItem[@"value"] = RCTNSStringFromString(item.value); - dictItem[@"label"] = RCTNSStringFromStringNilIfEmpty(item.label); + dictItem[@"value"] = RNCPickerConvertFollyDynamicToId(item.value); + dictItem[@"label"] = RNCPickerConvertFollyDynamicToId(item.label); dictItem[@"textColor"] = RCTUIColorFromSharedColor(item.textColor); dictItem[@"testID"] = RCTNSStringFromStringNilIfEmpty(item.testID); [items addObject:dictItem]; diff --git a/ios/RNCPickerFabricConversions.h b/ios/RNCPickerFabricConversions.h new file mode 100644 index 000000000..204a484ef --- /dev/null +++ b/ios/RNCPickerFabricConversions.h @@ -0,0 +1,106 @@ +#import +#import +#import + +// copied from RCTFollyConvert +static id RNCPickerConvertFollyDynamicToId(const folly::dynamic &dyn) +{ + // I could imagine an implementation which avoids copies by wrapping the + // dynamic in a derived class of NSDictionary. We can do that if profiling + // implies it will help. + + switch (dyn.type()) { + case folly::dynamic::NULLT: + return nil; + case folly::dynamic::BOOL: + return dyn.getBool() ? @YES : @NO; + case folly::dynamic::INT64: + return @(dyn.getInt()); + case folly::dynamic::DOUBLE: + return @(dyn.getDouble()); + case folly::dynamic::STRING: + return [[NSString alloc] initWithBytes:dyn.c_str() length:dyn.size() encoding:NSUTF8StringEncoding]; + case folly::dynamic::ARRAY: { + NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:dyn.size()]; + for (const auto &elem : dyn) { + id value = RNCPickerConvertFollyDynamicToId(elem); + if (value) { + [array addObject:value]; + } + } + return array; + } + case folly::dynamic::OBJECT: { + NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:dyn.size()]; + for (const auto &elem : dyn.items()) { + id key = RNCPickerConvertFollyDynamicToId(elem.first); + id value = RNCPickerConvertFollyDynamicToId(elem.second); + if (key && value) { + dict[key] = value; + } + } + return dict; + } + } +} + +static folly::dynamic RNCPickerRNCPickerConvertIdToFollyDynamic(id json) +{ + if (json == nil || json == (id)kCFNull) { + return nullptr; + } else if ([json isKindOfClass:[NSNumber class]]) { + const char *objCType = [json objCType]; + switch (objCType[0]) { + // This is a c++ bool or C99 _Bool. On some platforms, BOOL is a bool. + case _C_BOOL: + return (bool)[json boolValue]; + case _C_CHR: + // On some platforms, objc BOOL is a signed char, but it + // might also be a small number. Use the same hack JSC uses + // to distinguish them: + // https://phabricator.intern.facebook.com/diffusion/FBS/browse/master/fbobjc/xplat/third-party/jsc/safari-600-1-4-17/JavaScriptCore/API/JSValue.mm;b8ee03916489f8b12143cd5c0bca546da5014fc9$901 + if ([json isKindOfClass:[@YES class]]) { + return (bool)[json boolValue]; + } else { + return [json longLongValue]; + } + case _C_UCHR: + case _C_SHT: + case _C_USHT: + case _C_INT: + case _C_UINT: + case _C_LNG: + case _C_ULNG: + case _C_LNG_LNG: + case _C_ULNG_LNG: + return [json longLongValue]; + + case _C_FLT: + case _C_DBL: + return [json doubleValue]; + + // default: + // fall through + } + } else if ([json isKindOfClass:[NSString class]]) { + NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding]; + return std::string(reinterpret_cast(data.bytes), data.length); + } else if ([json isKindOfClass:[NSArray class]]) { + folly::dynamic array = folly::dynamic::array; + for (id element in json) { + array.push_back(RNCPickerRNCPickerConvertIdToFollyDynamic(element)); + } + return array; + } else if ([json isKindOfClass:[NSDictionary class]]) { + __block folly::dynamic object = folly::dynamic::object(); + + [json enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, __unused BOOL *stop) { + object.insert(RNCPickerRNCPickerConvertIdToFollyDynamic(key), RNCPickerRNCPickerConvertIdToFollyDynamic(value)); + }]; + + return object; + } + + return nil; +} + diff --git a/js/RNCPickerNativeComponent.js b/js/RNCPickerNativeComponent.js index e8960b9d7..370145345 100644 --- a/js/RNCPickerNativeComponent.js +++ b/js/RNCPickerNativeComponent.js @@ -23,13 +23,13 @@ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNati import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands'; type PickerIOSChangeEvent = $ReadOnly<{| - newValue: string, + newValue: UnsafeMixed, newIndex: Int32, |}>; type RNCPickerIOSTypeItemType = $ReadOnly<{| - label: ?string, - value: ?string, + label: ?UnsafeMixed, + value: ?UnsafeMixed, textColor: ?ColorValue, testID: ?string, |}>; From fcd1535ef94e340caeba5ced8811bc38275259f6 Mon Sep 17 00:00:00 2001 From: Wojciech Lewicki Date: Mon, 25 Nov 2024 13:54:15 +0100 Subject: [PATCH 2/2] fix: change values in JS too --- js/PickerIOS.ios.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/PickerIOS.ios.js b/js/PickerIOS.ios.js index 176e38cc8..2d8623d44 100644 --- a/js/PickerIOS.ios.js +++ b/js/PickerIOS.ios.js @@ -137,12 +137,12 @@ const PickerIOSWithForwardedRef: React.AbstractComponent< if (child === null) { return null; } - if (String(child.props.value) === String(selectedValue)) { + if (child.props.value === selectedValue) { selectedIndex = index; } return { - value: String(child.props.value), - label: String(child.props.label), + value: child.props.value, + label: child.props.label, textColor: processColor(child.props.color), testID: child.props.testID, }; @@ -162,7 +162,7 @@ const PickerIOSWithForwardedRef: React.AbstractComponent< child: $FlowFixMe, index: number, ) { - if (String(child.props.value) === String(selectedValue)) { + if (child.props.value === selectedValue) { jsValue = index; } });