Avoid sending out multiple requests for the same image
Reviewed By: majak Differential Revision: D3755074 fbshipit-source-id: fea782fcb99e6b977fb52231d378aaab4685d480
This commit is contained in:
parent
dadfe40f55
commit
86fbf2386b
|
@ -52,7 +52,6 @@ static NSDictionary *onLoadParamsForSource(RCTImageSource *source)
|
||||||
|
|
||||||
@interface RCTImageView ()
|
@interface RCTImageView ()
|
||||||
|
|
||||||
@property (nonatomic, strong) RCTImageSource *imageSource;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onLoadStart;
|
@property (nonatomic, copy) RCTDirectEventBlock onLoadStart;
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onProgress;
|
@property (nonatomic, copy) RCTDirectEventBlock onProgress;
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onError;
|
@property (nonatomic, copy) RCTDirectEventBlock onError;
|
||||||
|
@ -64,6 +63,13 @@ static NSDictionary *onLoadParamsForSource(RCTImageSource *source)
|
||||||
@implementation RCTImageView
|
@implementation RCTImageView
|
||||||
{
|
{
|
||||||
__weak RCTBridge *_bridge;
|
__weak RCTBridge *_bridge;
|
||||||
|
|
||||||
|
// The image source that's currently displayed
|
||||||
|
RCTImageSource *_imageSource;
|
||||||
|
|
||||||
|
// The image source that's being loaded from the network
|
||||||
|
RCTImageSource *_pendingImageSource;
|
||||||
|
|
||||||
CGSize _targetSize;
|
CGSize _targetSize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,10 +104,10 @@ static NSDictionary *onLoadParamsForSource(RCTImageSource *source)
|
||||||
|
|
||||||
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
|
|
||||||
- (void)updateImage
|
- (void)updateWithImage:(UIImage *)image
|
||||||
{
|
{
|
||||||
UIImage *image = self.image;
|
|
||||||
if (!image) {
|
if (!image) {
|
||||||
|
self.image = nil;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +122,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
// Applying capInsets of 0 will switch the "resizingMode" of the image to "tile" which is undesired
|
// Applying capInsets of 0 will switch the "resizingMode" of the image to "tile" which is undesired
|
||||||
image = [image resizableImageWithCapInsets:_capInsets resizingMode:UIImageResizingModeStretch];
|
image = [image resizableImageWithCapInsets:_capInsets resizingMode:UIImageResizingModeStretch];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply trilinear filtering to smooth out mis-sized images
|
// Apply trilinear filtering to smooth out mis-sized images
|
||||||
self.layer.minificationFilter = kCAFilterTrilinear;
|
self.layer.minificationFilter = kCAFilterTrilinear;
|
||||||
self.layer.magnificationFilter = kCAFilterTrilinear;
|
self.layer.magnificationFilter = kCAFilterTrilinear;
|
||||||
|
@ -126,9 +133,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
- (void)setImage:(UIImage *)image
|
- (void)setImage:(UIImage *)image
|
||||||
{
|
{
|
||||||
image = image ?: _defaultImage;
|
image = image ?: _defaultImage;
|
||||||
if (image != super.image) {
|
if (image != self.image) {
|
||||||
super.image = image;
|
[self updateWithImage:image];
|
||||||
[self updateImage];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +156,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
[self reloadImage];
|
[self reloadImage];
|
||||||
} else {
|
} else {
|
||||||
_capInsets = capInsets;
|
_capInsets = capInsets;
|
||||||
[self updateImage];
|
[self updateWithImage:self.image];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,7 +165,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
{
|
{
|
||||||
if (_renderingMode != renderingMode) {
|
if (_renderingMode != renderingMode) {
|
||||||
_renderingMode = renderingMode;
|
_renderingMode = renderingMode;
|
||||||
[self updateImage];
|
[self updateWithImage:self.image];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,12 +177,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)sourceNeedsReload
|
|
||||||
{
|
|
||||||
// If capInsets are set, image doesn't need reloading when resized
|
|
||||||
return UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setResizeMode:(RCTResizeMode)resizeMode
|
- (void)setResizeMode:(RCTResizeMode)resizeMode
|
||||||
{
|
{
|
||||||
if (_resizeMode != resizeMode) {
|
if (_resizeMode != resizeMode) {
|
||||||
|
@ -190,7 +190,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
self.contentMode = (UIViewContentMode)resizeMode;
|
self.contentMode = (UIViewContentMode)resizeMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([self sourceNeedsReload]) {
|
if ([self shouldReloadImageSourceAfterResize]) {
|
||||||
[self reloadImage];
|
[self reloadImage];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,6 +203,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
previousCancellationBlock();
|
previousCancellationBlock();
|
||||||
_reloadImageCancellationBlock = nil;
|
_reloadImageCancellationBlock = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_pendingImageSource = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)clearImage
|
- (void)clearImage
|
||||||
|
@ -229,6 +231,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
if (![self hasMultipleSources]) {
|
if (![self hasMultipleSources]) {
|
||||||
return _imageSources.firstObject;
|
return _imageSources.firstObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to wait for layout pass before deciding.
|
// Need to wait for layout pass before deciding.
|
||||||
if (CGSizeEqualToSize(size, CGSizeZero)) {
|
if (CGSizeEqualToSize(size, CGSizeZero)) {
|
||||||
return nil;
|
return nil;
|
||||||
|
@ -252,9 +255,19 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
return bestSource;
|
return bestSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)desiredImageSourceDidChange
|
- (BOOL)shouldReloadImageSourceAfterResize
|
||||||
{
|
{
|
||||||
return ![[self imageSourceForSize:self.frame.size] isEqual:_imageSource];
|
// If capInsets are set, image doesn't need reloading when resized
|
||||||
|
return UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)shouldChangeImageSource
|
||||||
|
{
|
||||||
|
// We need to reload if the desired image source is different from the current image
|
||||||
|
// source AND the image load that's pending
|
||||||
|
RCTImageSource *desiredImageSource = [self imageSourceForSize:self.frame.size];
|
||||||
|
return ![desiredImageSource isEqual:_imageSource] &&
|
||||||
|
![desiredImageSource isEqual:_pendingImageSource];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)reloadImage
|
- (void)reloadImage
|
||||||
|
@ -262,9 +275,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
[self cancelImageLoad];
|
[self cancelImageLoad];
|
||||||
|
|
||||||
RCTImageSource *source = [self imageSourceForSize:self.frame.size];
|
RCTImageSource *source = [self imageSourceForSize:self.frame.size];
|
||||||
_imageSource = source;
|
_pendingImageSource = source;
|
||||||
|
|
||||||
if (_imageSource && self.frame.size.width > 0 && self.frame.size.height > 0) {
|
if (source && self.frame.size.width > 0 && self.frame.size.height > 0) {
|
||||||
if (_onLoadStart) {
|
if (_onLoadStart) {
|
||||||
_onLoadStart(nil);
|
_onLoadStart(nil);
|
||||||
}
|
}
|
||||||
|
@ -284,7 +297,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero)) {
|
if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero)) {
|
||||||
// Don't resize images that use capInsets
|
// Don't resize images that use capInsets
|
||||||
imageSize = CGSizeZero;
|
imageSize = CGSizeZero;
|
||||||
imageScale = _imageSource.scale;
|
imageScale = source.scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
__weak RCTImageView *weakSelf = self;
|
__weak RCTImageView *weakSelf = self;
|
||||||
|
@ -307,7 +320,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
|
|
||||||
- (void)imageLoaderLoadedImage:(UIImage *)loadedImage error:(NSError *)error forImageSource:(RCTImageSource *)source
|
- (void)imageLoaderLoadedImage:(UIImage *)loadedImage error:(NSError *)error forImageSource:(RCTImageSource *)source
|
||||||
{
|
{
|
||||||
if (![source isEqual:_imageSource]) {
|
if (![source isEqual:_pendingImageSource]) {
|
||||||
// Bail out if source has changed since we started loading
|
// Bail out if source has changed since we started loading
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -323,6 +336,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
}
|
}
|
||||||
|
|
||||||
void (^setImageBlock)(UIImage *) = ^(UIImage *image) {
|
void (^setImageBlock)(UIImage *) = ^(UIImage *image) {
|
||||||
|
self->_imageSource = source;
|
||||||
|
self->_pendingImageSource = nil;
|
||||||
|
|
||||||
if (image.reactKeyframeAnimation) {
|
if (image.reactKeyframeAnimation) {
|
||||||
[self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
|
[self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
|
||||||
} else {
|
} else {
|
||||||
|
@ -361,27 +377,24 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
{
|
{
|
||||||
[super reactSetFrame:frame];
|
[super reactSetFrame:frame];
|
||||||
|
|
||||||
if (!self.image || self.image == _defaultImage) {
|
// If we didn't load an image yet, or the new frame triggers a different image source
|
||||||
|
// to be loaded, reload to swap to the proper image source.
|
||||||
|
if ([self shouldChangeImageSource]) {
|
||||||
_targetSize = frame.size;
|
_targetSize = frame.size;
|
||||||
[self reloadImage];
|
[self reloadImage];
|
||||||
} else if ([self sourceNeedsReload]) {
|
} else if ([self shouldReloadImageSourceAfterResize]) {
|
||||||
CGSize imageSize = self.image.size;
|
CGSize imageSize = self.image.size;
|
||||||
CGSize idealSize = RCTTargetSize(imageSize, self.image.scale, frame.size,
|
CGSize idealSize = RCTTargetSize(imageSize, self.image.scale, frame.size,
|
||||||
RCTScreenScale(), (RCTResizeMode)self.contentMode, YES);
|
RCTScreenScale(), (RCTResizeMode)self.contentMode, YES);
|
||||||
|
|
||||||
if ([self desiredImageSourceDidChange]) {
|
if (RCTShouldReloadImageForSizeChange(imageSize, idealSize) &&
|
||||||
// Reload to swap to the proper image source.
|
RCTShouldReloadImageForSizeChange(_targetSize, idealSize)) {
|
||||||
|
RCTLogInfo(@"Reloading image %@ as size %@", [_imageSources firstObject].request.URL.absoluteString, NSStringFromCGSize(idealSize));
|
||||||
|
|
||||||
|
// If the existing image or an image being loaded are not the right
|
||||||
|
// size, reload the asset in case there is a better size available.
|
||||||
_targetSize = idealSize;
|
_targetSize = idealSize;
|
||||||
[self reloadImage];
|
[self reloadImage];
|
||||||
} else if (RCTShouldReloadImageForSizeChange(imageSize, idealSize)) {
|
|
||||||
if (RCTShouldReloadImageForSizeChange(_targetSize, idealSize)) {
|
|
||||||
RCTLogInfo(@"[PERF IMAGEVIEW] Reloading image %@ as size %@", _imageSource.request.URL.absoluteString, NSStringFromCGSize(idealSize));
|
|
||||||
|
|
||||||
// If the existing image or an image being loaded are not the right
|
|
||||||
// size, reload the asset in case there is a better size available.
|
|
||||||
_targetSize = idealSize;
|
|
||||||
[self reloadImage];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -396,7 +409,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
// requests that have gotten "stuck" from the queue, unblocking other images
|
// requests that have gotten "stuck" from the queue, unblocking other images
|
||||||
// from loading.
|
// from loading.
|
||||||
[self cancelImageLoad];
|
[self cancelImageLoad];
|
||||||
} else if (!self.image || self.image == _defaultImage) {
|
} else if ([self shouldChangeImageSource]) {
|
||||||
[self reloadImage];
|
[self reloadImage];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue