Skip to content

Commit

Permalink
Use SDWebImage instead of RCTImageLoader in RCTImageView
Browse files Browse the repository at this point in the history
  • Loading branch information
janicduplessis committed Nov 22, 2023
1 parent a66cda9 commit 40fc909
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 64 deletions.
4 changes: 3 additions & 1 deletion packages/react-native/Libraries/Image/RCTImageView.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@

#import <React/RCTResizeMode.h>
#import <React/RCTView.h>
#import <SDWebImage/SDWebImage.h>
#import <UIKit/UIKit.h>

@class RCTBridge;
@class RCTImageSource;

@interface RCTImageView : RCTView
@interface RCTImageView : SDAnimatedImageView

- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;

Expand All @@ -23,5 +24,6 @@
@property (nonatomic, assign) CGFloat blurRadius;
@property (nonatomic, assign) RCTResizeMode resizeMode;
@property (nonatomic, copy) NSString *internal_analyticTag;
@property (nonatomic, assign) NSInteger fadeDuration;

@end
152 changes: 89 additions & 63 deletions packages/react-native/Libraries/Image/RCTImageView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#import <React/RCTUIImageViewAnimated.h>
#import <React/RCTUtils.h>
#import <React/UIView+React.h>
#import <SDWebImage/UIImageView+WebCache.h>
#import <SDWebImagePhotosPlugin/SDWebImagePhotosPlugin.h>

/**
* Determines whether an image of `currentSize` should be reloaded for display
Expand Down Expand Up @@ -77,18 +79,14 @@ @implementation RCTImageView {
// Whether the latest change of props requires the image to be reloaded
BOOL _needsReload;

RCTUIImageViewAnimated *_imageView;

RCTImageURLLoaderRequest *_loaderRequest;
}

- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super initWithFrame:CGRectZero])) {
_bridge = bridge;
_imageView = [RCTUIImageViewAnimated new];
_imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:_imageView];
_fadeDuration = -1;

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
Expand All @@ -114,11 +112,10 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge

RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame)

- (void)updateWithImage:(UIImage *)image
- (UIImage *)updateWithImage:(UIImage *)image
{
if (!image) {
_imageView.image = nil;
return;
return nil;
}

// Apply rendering mode
Expand All @@ -134,25 +131,20 @@ - (void)updateWithImage:(UIImage *)image
}

// Apply trilinear filtering to smooth out missized images
_imageView.layer.minificationFilter = kCAFilterTrilinear;
_imageView.layer.magnificationFilter = kCAFilterTrilinear;
self.layer.minificationFilter = kCAFilterTrilinear;
self.layer.magnificationFilter = kCAFilterTrilinear;

_imageView.image = image;
return image;
}

- (void)setImage:(UIImage *)image
{
image = image ?: _defaultImage;
if (image != self.image) {
[self updateWithImage:image];
[super setImage:[self updateWithImage:image]];
}
}

- (UIImage *)image
{
return _imageView.image;
}

- (void)setBlurRadius:(CGFloat)blurRadius
{
if (blurRadius != _blurRadius) {
Expand All @@ -171,7 +163,7 @@ - (void)setCapInsets:(UIEdgeInsets)capInsets
_needsReload = YES;
} else {
_capInsets = capInsets;
[self updateWithImage:self.image];
self.image = [self updateWithImage:self.image];
}
}
}
Expand All @@ -180,7 +172,7 @@ - (void)setRenderingMode:(UIImageRenderingMode)renderingMode
{
if (_renderingMode != renderingMode) {
_renderingMode = renderingMode;
[self updateWithImage:self.image];
self.image = [self updateWithImage:self.image];
}
}

Expand All @@ -200,9 +192,9 @@ - (void)setResizeMode:(RCTResizeMode)resizeMode
if (_resizeMode == RCTResizeModeRepeat) {
// Repeat resize mode is handled by the UIImage. Use scale to fill
// so the repeated image fills the UIImageView.
_imageView.contentMode = UIViewContentModeScaleToFill;
self.contentMode = UIViewContentModeScaleToFill;
} else {
_imageView.contentMode = (UIViewContentMode)resizeMode;
self.contentMode = (UIViewContentMode)resizeMode;
}

if ([self shouldReloadImageSourceAfterResize]) {
Expand Down Expand Up @@ -307,52 +299,86 @@ - (void)reloadImage
_onLoadStart(nil);
}

RCTImageLoaderProgressBlock progressHandler = nil;
if (self.onProgress) {
RCTDirectEventBlock onProgress = self.onProgress;
progressHandler = ^(int64_t loaded, int64_t total) {
onProgress(@{
@"loaded" : @((double)loaded),
@"total" : @((double)total),
});
};
}

__weak RCTImageView *weakSelf = self;
RCTImageLoaderPartialLoadBlock partialLoadHandler = ^(UIImage *image) {
[weakSelf imageLoaderLoadedImage:image error:nil forImageSource:source partial:YES];
};

CGSize imageSize = self.bounds.size;
CGFloat imageScale = RCTScreenScale();
if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero)) {
// Don't resize images that use capInsets
imageSize = CGSizeZero;
imageScale = source.scale;
}
if (RCTIsLocalAssetURL(source.request.URL)) {
RCTImageLoaderProgressBlock progressHandler = nil;
if (self.onProgress) {
RCTDirectEventBlock onProgress = self.onProgress;
progressHandler = ^(int64_t loaded, int64_t total) {
onProgress(@{
@"loaded" : @((double)loaded),
@"total" : @((double)total),
});
};
}

RCTImageLoaderCompletionBlockWithMetadata completionHandler = ^(NSError *error, UIImage *loadedImage, id metadata) {
[weakSelf imageLoaderLoadedImage:loadedImage error:error forImageSource:source partial:NO];
};
RCTImageLoaderPartialLoadBlock partialLoadHandler = ^(UIImage *image) {
[weakSelf imageLoaderLoadedImage:image error:nil forImageSource:source partial:YES];
};

if (!_imageLoader) {
_imageLoader = [_bridge moduleForName:@"ImageLoader" lazilyLoadIfNecessary:YES];
}
CGSize imageSize = self.bounds.size;
CGFloat imageScale = RCTScreenScale();
if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero)) {
// Don't resize images that use capInsets
imageSize = CGSizeZero;
imageScale = source.scale;
}

RCTImageURLLoaderRequest *loaderRequest =
[_imageLoader loadImageWithURLRequest:source.request
size:imageSize
scale:imageScale
clipped:NO
resizeMode:_resizeMode
priority:RCTImageLoaderPriorityImmediate
attribution:{.nativeViewTag = [self.reactTag intValue],
.surfaceId = [self.rootTag intValue],
.analyticTag = self.internal_analyticTag}
progressBlock:progressHandler
partialLoadBlock:partialLoadHandler
completionBlock:completionHandler];
_loaderRequest = loaderRequest;
RCTImageLoaderCompletionBlockWithMetadata completionHandler =
^(NSError *error, UIImage *loadedImage, id metadata) {
[weakSelf imageLoaderLoadedImage:loadedImage error:error forImageSource:source partial:NO];
};

RCTImageURLLoaderRequest *loaderRequest = [[_bridge moduleForName:@"ImageLoader" lazilyLoadIfNecessary:YES]
loadImageWithURLRequest:source.request
size:imageSize
scale:imageScale
clipped:NO
resizeMode:_resizeMode
priority:RCTImageLoaderPriorityImmediate
attribution:{.nativeViewTag = [self.reactTag intValue],
.surfaceId = [self.rootTag intValue],
.analyticTag = self.internal_analyticTag}
progressBlock:progressHandler
partialLoadBlock:partialLoadHandler
completionBlock:completionHandler];
_loaderRequest = loaderRequest;
} else {
NSURL *url;
// Rewrite assets library to use the same scheme that SDWebImage expects.
if ([source.request.URL.scheme isEqualToString:@"assets-library"] ||
[source.request.URL.scheme isEqualToString:@"ph"]) {
url = [NSURL sd_URLWithAssetLocalIdentifier:source.request.URL.path];
} else {
url = source.request.URL;
}
SDImageLoaderProgressBlock progressHandler = nil;
if (_onProgress) {
progressHandler = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL *_Nullable targetURL) {
self->_onProgress(@{
@"loaded" : @((double)receivedSize),
@"total" : @((double)expectedSize),
});
};
}
SDExternalCompletionBlock completionHandler =
^(UIImage *_Nullable image, NSError *_Nullable error, SDImageCacheType cacheType, NSURL *_Nullable imageURL) {
if (image && (cacheType == SDImageCacheTypeNone || cacheType == SDImageCacheTypeDisk)) {
weakSelf.alpha = 0;
[UIView animateWithDuration:weakSelf.fadeDuration >= 0 ? weakSelf.fadeDuration / 1000.0 : 0.3
animations:^{
weakSelf.alpha = 1;
}];
}
[weakSelf imageLoaderLoadedImage:image error:error forImageSource:source partial:NO];
};

[self sd_setImageWithURL:url
placeholderImage:_defaultImage
options:SDWebImageRetryFailed
progress:progressHandler
completed:completionHandler];
}
} else {
[self cancelAndClearImageLoad];
}
Expand Down
19 changes: 19 additions & 0 deletions packages/react-native/Libraries/Image/RCTImageViewManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@

#import <React/RCTConvert.h>
#import <React/RCTImageSource.h>
#import <SDWebImageAVIFCoder/SDImageAVIFCoder.h>
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
#import <SDWebImagePhotosPlugin/SDWebImagePhotosPlugin.h>
#import <SDWebImage/SDWebImage.h>

#import <React/RCTImageLoaderProtocol.h>
#import <React/RCTImageShadowView.h>
Expand All @@ -30,9 +34,24 @@ - (UIView *)view
return [[RCTImageView alloc] initWithBridge:self.bridge];
}

- (void)setBridge:(RCTBridge *)bridge
{
[super setBridge:bridge];

SDImageAVIFCoder *AVIFCoder = [SDImageAVIFCoder sharedCoder];
[SDImageCodersManager.sharedManager addCoder:AVIFCoder];
SDImageWebPCoder *webPCoder = [SDImageWebPCoder sharedCoder];
[SDImageCodersManager.sharedManager addCoder:webPCoder];
// Supports HTTP URL as well as Photos URL globally
SDImageLoadersManager.sharedManager.loaders = @[SDWebImageDownloader.sharedDownloader, SDImagePhotosLoader.sharedLoader];
// Replace default manager's loader implementation
SDWebImageManager.defaultImageLoader = SDImageLoadersManager.sharedManager;
}

RCT_EXPORT_VIEW_PROPERTY(blurRadius, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
RCT_REMAP_VIEW_PROPERTY(defaultSource, defaultImage, UIImage)
RCT_EXPORT_VIEW_PROPERTY(fadeDuration, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(onLoadStart, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock)
Expand Down
6 changes: 6 additions & 0 deletions packages/react-native/Libraries/Image/React-RCTImage.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,10 @@ Pod::Spec.new do |s|
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
add_dependency(s, "React-NativeModulesApple")

s.dependency 'SDWebImage'
s.dependency 'SDWebImageWebPCoder'
s.dependency 'SDWebImageAVIFCoder'
s.dependency 'libavif/libdav1d'
s.dependency 'SDWebImagePhotosPlugin'

end

0 comments on commit 40fc909

Please sign in to comment.