From 40fc909dcb305ec3d5088c38966efc998165a31c Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Wed, 28 Dec 2016 00:47:20 -0500 Subject: [PATCH] Use SDWebImage instead of RCTImageLoader in RCTImageView --- .../Libraries/Image/RCTImageView.h | 4 +- .../Libraries/Image/RCTImageView.mm | 152 ++++++++++-------- .../Libraries/Image/RCTImageViewManager.mm | 19 +++ .../Libraries/Image/React-RCTImage.podspec | 6 + 4 files changed, 117 insertions(+), 64 deletions(-) diff --git a/packages/react-native/Libraries/Image/RCTImageView.h b/packages/react-native/Libraries/Image/RCTImageView.h index da7f860228c6bb..c2b58e4d3e24d3 100644 --- a/packages/react-native/Libraries/Image/RCTImageView.h +++ b/packages/react-native/Libraries/Image/RCTImageView.h @@ -7,12 +7,13 @@ #import #import +#import #import @class RCTBridge; @class RCTImageSource; -@interface RCTImageView : RCTView +@interface RCTImageView : SDAnimatedImageView - (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; @@ -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 diff --git a/packages/react-native/Libraries/Image/RCTImageView.mm b/packages/react-native/Libraries/Image/RCTImageView.mm index 3b3878fccb6495..a44d06582fd7be 100644 --- a/packages/react-native/Libraries/Image/RCTImageView.mm +++ b/packages/react-native/Libraries/Image/RCTImageView.mm @@ -16,6 +16,8 @@ #import #import #import +#import +#import /** * Determines whether an image of `currentSize` should be reloaded for display @@ -77,8 +79,6 @@ @implementation RCTImageView { // Whether the latest change of props requires the image to be reloaded BOOL _needsReload; - RCTUIImageViewAnimated *_imageView; - RCTImageURLLoaderRequest *_loaderRequest; } @@ -86,9 +86,7 @@ - (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 @@ -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 @@ -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) { @@ -171,7 +163,7 @@ - (void)setCapInsets:(UIEdgeInsets)capInsets _needsReload = YES; } else { _capInsets = capInsets; - [self updateWithImage:self.image]; + self.image = [self updateWithImage:self.image]; } } } @@ -180,7 +172,7 @@ - (void)setRenderingMode:(UIImageRenderingMode)renderingMode { if (_renderingMode != renderingMode) { _renderingMode = renderingMode; - [self updateWithImage:self.image]; + self.image = [self updateWithImage:self.image]; } } @@ -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]) { @@ -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]; } diff --git a/packages/react-native/Libraries/Image/RCTImageViewManager.mm b/packages/react-native/Libraries/Image/RCTImageViewManager.mm index 19ecaf7ee2c938..3e3a1cbc94d165 100644 --- a/packages/react-native/Libraries/Image/RCTImageViewManager.mm +++ b/packages/react-native/Libraries/Image/RCTImageViewManager.mm @@ -11,6 +11,10 @@ #import #import +#import +#import +#import +#import #import #import @@ -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) diff --git a/packages/react-native/Libraries/Image/React-RCTImage.podspec b/packages/react-native/Libraries/Image/React-RCTImage.podspec index 471af8914096ff..caaf8504840909 100644 --- a/packages/react-native/Libraries/Image/React-RCTImage.podspec +++ b/packages/react-native/Libraries/Image/React-RCTImage.podspec @@ -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