-
Notifications
You must be signed in to change notification settings - Fork 24.6k
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) { | ||
strongSelf->_imageView.image = finalImage; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we emit an There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||
} | ||
[item setObject:attributedText.string forKey:(id)kUTTypeUTF8PlainText]; | ||
Check warning on line 288 in packages/react-native/Libraries/Text/Text/RCTTextView.mm
|
||
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
|
||
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
|
||
} | ||
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
|
||
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
|
||
- (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
|
||
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
|
||
_bridge = bridge; | ||
_moduleClass = moduleClass; | ||
_moduleProvider = [moduleProvider copy]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but what if we have an
image
already? I mean, what happens if we change the imageSource prop?There was a problem hiding this comment.
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.