Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] Fabric: Support defaultSource prop of Image component #46554

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -70,6 +70,41 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
[super updateProps:props oldProps:oldProps];
}

- (void)_setImage:(UIImage *)image setImageBlock:(void (^)(UIImage *finalImage))setImageBlock
{
const auto &imageProps = static_cast<const ImageProps &>(*_props);

if (imageProps.tintColor) {
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}

if (imageProps.resizeMode == ImageResizeMode::Repeat) {
image = [image resizableImageWithCapInsets:RCTUIEdgeInsetsFromEdgeInsets(imageProps.capInsets)
resizingMode:UIImageResizingModeTile];
} else if (imageProps.capInsets != EdgeInsets()) {
// Applying capInsets of 0 will switch the "resizingMode" of the image to "tile" which is undesired.
image = [image resizableImageWithCapInsets:RCTUIEdgeInsetsFromEdgeInsets(imageProps.capInsets)
resizingMode:UIImageResizingModeStretch];
}

if (imageProps.blurRadius > __FLT_EPSILON__) {
// Blur on a background thread to avoid blocking interaction.
CGFloat blurRadius = imageProps.blurRadius;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *blurredImage = RCTBlurredImageWithRadius(image, blurRadius);
RCTExecuteOnMainQueue(^{
if (setImageBlock) {
setImageBlock(blurredImage);
}
});
});
} else {
if (setImageBlock) {
setImageBlock(image);
}
}
}

- (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState
{
RCTAssert(state, @"`state` must not be null.");
@@ -86,6 +121,23 @@ - (void)updateState:(const State::Shared &)state oldState:(const State::Shared &

if (!havePreviousData ||
(newImageState && newImageState->getData().getImageSource() != oldImageState->getData().getImageSource())) {
const auto &imageProps = static_cast<const ImageProps &>(*_props);
if (!imageProps.defaultSources.empty()) {
UIImage *defaultImage = [self _getUIImageWithImageSource:imageProps.defaultSources.front()];

if (defaultImage) {
__weak RCTImageComponentView *weakSelf = self;
[self _setImage:defaultImage
setImageBlock:^(UIImage *finalImage) {
RCTImageComponentView *strongSelf = weakSelf;
if (strongSelf) {
if (!strongSelf->_imageView.image) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (strongSelf) {
if (!strongSelf->_imageView.image) {
if (strongSelf && !strongSelf->_imageView.image) {

but what if we have an image already? I mean, what happens if we change the imageSource prop?

Copy link
Contributor Author

@zhongwuzw zhongwuzw Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have an image already, we would not reset default image and just only to load new image source. The same logic as old arch.

strongSelf->_imageView.image = finalImage;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we emit an OnLoadEnd event here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same as old arch, we only fire image load events for image source.

}
}
}];
}
}
// Loading actually starts a little before this, but this is the first time we know
// the image is loading and can fire an event from this component
static_cast<const ImageEventEmitter &>(*_eventEmitter).onLoadStart();
@@ -95,6 +147,62 @@ - (void)updateState:(const State::Shared &)state oldState:(const State::Shared &
}
}

- (UIImage *)_getUIImageWithImageSource:(const ImageSource &)imageSource
{
if (imageSource.uri.empty()) {
return nil;
}
UIImage *image;
NSURL *URL = [NSURL URLWithString:[[NSString alloc] initWithBytesNoCopy:(void *)imageSource.uri.c_str()
length:imageSource.uri.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO]];
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) {
RCTLogError(@"%@ is not an image. File not found.", URL);
}
} else if ([scheme isEqualToString:@"data"]) {
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
} else if ([scheme isEqualToString:@"http"] && imageSource.type == ImageSource::Type::Local) {
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
} else {
RCTLogError(@"%@ is not an image. Only local files or data URIs are supported.", URL);
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];
}

facebook::react::Size imageSize = {image.size.width, image.size.height};
if (!(imageSource.size == facebook::react::Size()) && !(imageSource.size == imageSize)) {
RCTLogInfo(
@"Image source %@ size %@ does not match loaded image size %@.",
URL.path.lastPathComponent,
NSStringFromCGSize(CGSizeMake(imageSource.size.width, imageSource.size.height)),
NSStringFromCGSize(image.size));
}
return image;
}

- (void)_setStateAndResubscribeImageResponseObserver:(const ImageShadowNode::ConcreteState::Shared &)state
{
if (_state) {
@@ -142,33 +250,14 @@ - (void)didReceiveImage:(UIImage *)image metadata:(id)metadata fromObserver:(con
static_cast<const ImageEventEmitter &>(*_eventEmitter).onLoad(imageSource);
static_cast<const ImageEventEmitter &>(*_eventEmitter).onLoadEnd();

const auto &imageProps = static_cast<const ImageProps &>(*_props);

if (imageProps.tintColor) {
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}

if (imageProps.resizeMode == ImageResizeMode::Repeat) {
image = [image resizableImageWithCapInsets:RCTUIEdgeInsetsFromEdgeInsets(imageProps.capInsets)
resizingMode:UIImageResizingModeTile];
} else if (imageProps.capInsets != EdgeInsets()) {
// Applying capInsets of 0 will switch the "resizingMode" of the image to "tile" which is undesired.
image = [image resizableImageWithCapInsets:RCTUIEdgeInsetsFromEdgeInsets(imageProps.capInsets)
resizingMode:UIImageResizingModeStretch];
}

if (imageProps.blurRadius > __FLT_EPSILON__) {
// Blur on a background thread to avoid blocking interaction.
CGFloat blurRadius = imageProps.blurRadius;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *blurredImage = RCTBlurredImageWithRadius(image, blurRadius);
RCTExecuteOnMainQueue(^{
self->_imageView.image = blurredImage;
});
});
} else {
self->_imageView.image = image;
}
__weak RCTImageComponentView *weakSelf = self;
[self _setImage:image
setImageBlock:^(UIImage *finalImage) {
RCTImageComponentView *strongSelf = weakSelf;
if (strongSelf) {
strongSelf->_imageView.image = finalImage;
}
}];
}

- (void)didReceiveProgress:(float)progress

Unchanged files with check annotations Beta

error:nil];
if (rtf) {
[item setObject:rtf forKey:(id)kUTTypeFlatRTFD];

Check warning on line 285 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (Hermes, NewArch)

'kUTTypeFlatRTFD' is deprecated: first deprecated in iOS 15.0 - Use UTTypeFlatRTFD or UTType.flatRTFD (swift) instead. [-Wdeprecated-declarations]

Check warning on line 285 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (Hermes, NewArch)

'kUTTypeFlatRTFD' is deprecated: first deprecated in iOS 15.0 - Use UTTypeFlatRTFD or UTType.flatRTFD (swift) instead. [-Wdeprecated-declarations]

Check warning on line 285 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (Hermes, OldArch)

'kUTTypeFlatRTFD' is deprecated: first deprecated in iOS 15.0 - Use UTTypeFlatRTFD or UTType.flatRTFD (swift) instead. [-Wdeprecated-declarations]

Check warning on line 285 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (Hermes, OldArch)

'kUTTypeFlatRTFD' is deprecated: first deprecated in iOS 15.0 - Use UTTypeFlatRTFD or UTType.flatRTFD (swift) instead. [-Wdeprecated-declarations]

Check warning on line 285 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (JSC, NewArch)

'kUTTypeFlatRTFD' is deprecated: first deprecated in iOS 15.0 - Use UTTypeFlatRTFD or UTType.flatRTFD (swift) instead. [-Wdeprecated-declarations]

Check warning on line 285 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (JSC, NewArch)

'kUTTypeFlatRTFD' is deprecated: first deprecated in iOS 15.0 - Use UTTypeFlatRTFD or UTType.flatRTFD (swift) instead. [-Wdeprecated-declarations]

Check warning on line 285 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (JSC, OldArch)

'kUTTypeFlatRTFD' is deprecated: first deprecated in iOS 15.0 - Use UTTypeFlatRTFD or UTType.flatRTFD (swift) instead. [-Wdeprecated-declarations]

Check warning on line 285 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (JSC, OldArch)

'kUTTypeFlatRTFD' is deprecated: first deprecated in iOS 15.0 - Use UTTypeFlatRTFD or UTType.flatRTFD (swift) instead. [-Wdeprecated-declarations]

Check warning on line 285 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester_ruby_3_2_0

'kUTTypeFlatRTFD' is deprecated: first deprecated in iOS 15.0 - Use UTTypeFlatRTFD or UTType.flatRTFD (swift) instead. [-Wdeprecated-declarations]

Check warning on line 285 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester_ruby_3_2_0

'kUTTypeFlatRTFD' is deprecated: first deprecated in iOS 15.0 - Use UTTypeFlatRTFD or UTType.flatRTFD (swift) instead. [-Wdeprecated-declarations]
}
[item setObject:attributedText.string forKey:(id)kUTTypeUTF8PlainText];

Check warning on line 288 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (Hermes, NewArch)

'kUTTypeUTF8PlainText' is deprecated: first deprecated in iOS 15.0 - Use UTTypeUTF8PlainText or UTType.utf8PlainText (swift) instead. [-Wdeprecated-declarations]

Check warning on line 288 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (Hermes, NewArch)

'kUTTypeUTF8PlainText' is deprecated: first deprecated in iOS 15.0 - Use UTTypeUTF8PlainText or UTType.utf8PlainText (swift) instead. [-Wdeprecated-declarations]

Check warning on line 288 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (Hermes, OldArch)

'kUTTypeUTF8PlainText' is deprecated: first deprecated in iOS 15.0 - Use UTTypeUTF8PlainText or UTType.utf8PlainText (swift) instead. [-Wdeprecated-declarations]

Check warning on line 288 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (Hermes, OldArch)

'kUTTypeUTF8PlainText' is deprecated: first deprecated in iOS 15.0 - Use UTTypeUTF8PlainText or UTType.utf8PlainText (swift) instead. [-Wdeprecated-declarations]

Check warning on line 288 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (JSC, NewArch)

'kUTTypeUTF8PlainText' is deprecated: first deprecated in iOS 15.0 - Use UTTypeUTF8PlainText or UTType.utf8PlainText (swift) instead. [-Wdeprecated-declarations]

Check warning on line 288 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (JSC, NewArch)

'kUTTypeUTF8PlainText' is deprecated: first deprecated in iOS 15.0 - Use UTTypeUTF8PlainText or UTType.utf8PlainText (swift) instead. [-Wdeprecated-declarations]

Check warning on line 288 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (JSC, OldArch)

'kUTTypeUTF8PlainText' is deprecated: first deprecated in iOS 15.0 - Use UTTypeUTF8PlainText or UTType.utf8PlainText (swift) instead. [-Wdeprecated-declarations]

Check warning on line 288 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester (JSC, OldArch)

'kUTTypeUTF8PlainText' is deprecated: first deprecated in iOS 15.0 - Use UTTypeUTF8PlainText or UTType.utf8PlainText (swift) instead. [-Wdeprecated-declarations]

Check warning on line 288 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester_ruby_3_2_0

'kUTTypeUTF8PlainText' is deprecated: first deprecated in iOS 15.0 - Use UTTypeUTF8PlainText or UTType.utf8PlainText (swift) instead. [-Wdeprecated-declarations]

Check warning on line 288 in packages/react-native/Libraries/Text/Text/RCTTextView.mm

GitHub Actions / test_ios_rntester_ruby_3_2_0

'kUTTypeUTF8PlainText' is deprecated: first deprecated in iOS 15.0 - Use UTTypeUTF8PlainText or UTType.utf8PlainText (swift) instead. [-Wdeprecated-declarations]
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.items = @[ item ];
NSMutableDictionary<NSString *, id<RCTBridgeModule>> *legacyInitializedModules = [NSMutableDictionary new];
if ([_delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
for (id<RCTBridgeModule> module in [_delegate extraModulesForBridge:nil]) {

Check warning on line 241 in packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm

GitHub Actions / test_ios_rntester (Hermes, NewArch)

'extraModulesForBridge:' is deprecated: Please make all native modules returned from this method TurboModule-compatible. [-Wdeprecated-declarations]

Check warning on line 241 in packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm

GitHub Actions / test_ios_rntester (Hermes, OldArch)

'extraModulesForBridge:' is deprecated: Please make all native modules returned from this method TurboModule-compatible. [-Wdeprecated-declarations]

Check warning on line 241 in packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm

GitHub Actions / test_ios_rntester (JSC, NewArch)

'extraModulesForBridge:' is deprecated: Please make all native modules returned from this method TurboModule-compatible. [-Wdeprecated-declarations]

Check warning on line 241 in packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm

GitHub Actions / test_ios_rntester (JSC, OldArch)

'extraModulesForBridge:' is deprecated: Please make all native modules returned from this method TurboModule-compatible. [-Wdeprecated-declarations]

Check warning on line 241 in packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm

GitHub Actions / test_ios_rntester_ruby_3_2_0

'extraModulesForBridge:' is deprecated: Please make all native modules returned from this method TurboModule-compatible. [-Wdeprecated-declarations]
if (!isTurboModuleInstance(module)) {
[legacyInitializedModules setObject:module forKey:RCTBridgeModuleNameForClass([module class])];
}
static jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value)
{
return jsi::String::createFromUtf8(runtime, [value UTF8String] ?: "");

Check warning on line 60 in packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm

GitHub Actions / test_ios_rntester (Hermes, NewArch)

use of GNU ?: conditional expression extension, omitting middle operand [-Wgnu-conditional-omitted-operand]

Check warning on line 60 in packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm

GitHub Actions / test_ios_rntester (Hermes, OldArch)

use of GNU ?: conditional expression extension, omitting middle operand [-Wgnu-conditional-omitted-operand]

Check warning on line 60 in packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm

GitHub Actions / test_ios_rntester (JSC, NewArch)

use of GNU ?: conditional expression extension, omitting middle operand [-Wgnu-conditional-omitted-operand]

Check warning on line 60 in packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm

GitHub Actions / test_ios_rntester (JSC, OldArch)

use of GNU ?: conditional expression extension, omitting middle operand [-Wgnu-conditional-omitted-operand]

Check warning on line 60 in packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm

GitHub Actions / test_ios_rntester_ruby_3_2_0

use of GNU ?: conditional expression extension, omitting middle operand [-Wgnu-conditional-omitted-operand]
}
static jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value)
{
const char *str = string.UTF8String;
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), result);

Check warning on line 242 in packages/react-native/React/Base/RCTUtils.m

GitHub Actions / test_ios_rntester_dynamic_frameworks (JSC)

'CC_MD5' is deprecated: first deprecated in iOS 13.0 - This function is cryptographically broken and should not be used in security contexts. Clients should migrate to SHA256 (or stronger). [-Wdeprecated-declarations]

Check warning on line 242 in packages/react-native/React/Base/RCTUtils.m

GitHub Actions / test_ios_rntester_dynamic_frameworks (Hermes)

'CC_MD5' is deprecated: first deprecated in iOS 13.0 - This function is cryptographically broken and should not be used in security contexts. Clients should migrate to SHA256 (or stronger). [-Wdeprecated-declarations]
return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0],
}
}
@implementation RCTSurfaceHostingProxyRootView

Check warning on line 52 in packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.mm

GitHub Actions / test_ios_rntester_dynamic_frameworks (JSC)

method override for the designated initializer of the superclass '-initWithSurface:sizeMeasureMode:' not found [-Wobjc-designated-initializers]

Check warning on line 52 in packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.mm

GitHub Actions / test_ios_rntester_dynamic_frameworks (Hermes)

method override for the designated initializer of the superclass '-initWithSurface:sizeMeasureMode:' not found [-Wobjc-designated-initializers]
- (instancetype)initWithSurface:(id<RCTSurfaceProtocol>)surface
{
UIButton *resumeButton = [UIButton buttonWithType:UIButtonTypeCustom];
[resumeButton setImage:[UIImage systemImageNamed:@"forward.frame.fill"] forState:UIControlStateNormal];
resumeButton.tintColor = [UIColor colorWithRed:0.37 green:0.37 blue:0.37 alpha:1];
resumeButton.adjustsImageWhenDisabled = NO;

Check warning on line 59 in packages/react-native/React/DevSupport/RCTPausedInDebuggerOverlayController.mm

GitHub Actions / test_ios_rntester_dynamic_frameworks (JSC)

'adjustsImageWhenDisabled' is deprecated: first deprecated in iOS 15.0 - This property is ignored when using UIButtonConfiguration, you may customize to replicate this behavior via a configurationUpdateHandler [-Wdeprecated-declarations]

Check warning on line 59 in packages/react-native/React/DevSupport/RCTPausedInDebuggerOverlayController.mm

GitHub Actions / test_ios_rntester_dynamic_frameworks (Hermes)

'adjustsImageWhenDisabled' is deprecated: first deprecated in iOS 15.0 - This property is ignored when using UIButtonConfiguration, you may customize to replicate this behavior via a configurationUpdateHandler [-Wdeprecated-declarations]
resumeButton.enabled = NO;
[NSLayoutConstraint activateConstraints:@[
[resumeButton.widthAnchor constraintEqualToConstant:48],
bundleManager:(RCTBundleManager *)bundleManager
callableJSModules:(RCTCallableJSModules *)callableJSModules
{
if (self = [super init]) {

Check warning on line 109 in packages/react-native/React/Base/RCTModuleData.mm

GitHub Actions / test_ios_rntester_dynamic_frameworks (JSC)

convenience initializer should not invoke an initializer on 'super' [-Wobjc-designated-initializers]

Check warning on line 109 in packages/react-native/React/Base/RCTModuleData.mm

GitHub Actions / test_ios_rntester_dynamic_frameworks (Hermes)

convenience initializer should not invoke an initializer on 'super' [-Wobjc-designated-initializers]
_bridge = bridge;
_moduleClass = moduleClass;
_moduleProvider = [moduleProvider copy];