From b34a85f4da87c62656ca9d29c5085a46e768d147 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 14 Jul 2015 04:06:17 -0700 Subject: [PATCH] Merged RCTStaticImage with FB internal version Summary: Merged RCTStaticImage with our internal RKStaticImage and ported over logic where assets are loaded at the optimal size and reloaded if the view size changes. --- .../Image/RCTImage.xcodeproj/project.pbxproj | 6 + Libraries/Image/RCTImageDownloader.m | 80 +--------- Libraries/Image/RCTImageLoader.h | 23 ++- Libraries/Image/RCTImageLoader.m | 90 ++++++++--- Libraries/Image/RCTImageUtils.h | 38 +++++ Libraries/Image/RCTImageUtils.m | 147 ++++++++++++++++++ Libraries/Image/RCTStaticImage.h | 1 + Libraries/Image/RCTStaticImage.m | 57 +++++++ Libraries/Image/RCTStaticImageManager.m | 37 +---- 9 files changed, 342 insertions(+), 137 deletions(-) create mode 100644 Libraries/Image/RCTImageUtils.h create mode 100644 Libraries/Image/RCTImageUtils.m diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj index 0127cfd8e..8ecabbafd 100644 --- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj +++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; }; 1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; }; 1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1345A8381B26592900583190 /* RCTImageRequestHandler.m */; }; + 134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */; }; 137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; }; 143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; }; 143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; }; @@ -43,6 +44,8 @@ 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = ""; }; 1345A8371B26592900583190 /* RCTImageRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageRequestHandler.h; sourceTree = ""; }; 1345A8381B26592900583190 /* RCTImageRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageRequestHandler.m; sourceTree = ""; }; + 134B00A01B54232B00EC8DFB /* RCTImageUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageUtils.h; sourceTree = ""; }; + 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtils.m; sourceTree = ""; }; 137620331B31C53500677FF0 /* RCTImagePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImagePickerManager.h; sourceTree = ""; }; 137620341B31C53500677FF0 /* RCTImagePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImagePickerManager.m; sourceTree = ""; }; 143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = ""; }; @@ -94,6 +97,8 @@ 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */, 1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */, 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */, + 134B00A01B54232B00EC8DFB /* RCTImageUtils.h */, + 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */, 58B5115E1A9E6B3D00147676 /* Products */, ); indentWidth = 2; @@ -175,6 +180,7 @@ 143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */, 03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */, 1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */, + 134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index 0f9cad198..697214249 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -10,6 +10,7 @@ #import "RCTImageDownloader.h" #import "RCTDownloadTaskWrapper.h" +#import "RCTImageUtils.h" #import "RCTLog.h" #import "RCTUtils.h" @@ -195,82 +196,3 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); } @end - -/** - * Returns the optimal context size for an image drawn using the clip rect - * returned by RCTClipRect. - */ -CGSize RCTTargetSizeForClipRect(CGRect clipRect) -{ - return (CGSize){ - clipRect.size.width + clipRect.origin.x * 2, - clipRect.size.height + clipRect.origin.y * 2 - }; -} - -/** - * This function takes an input content size & scale (typically from an image), - * a target size & scale that it will be drawn into (typically a CGContext) and - * then calculates the optimal rectangle to draw the image into so that it will - * be sized and positioned correctly if drawn using the specified content mode. - */ -CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, - CGSize destSize, CGFloat destScale, - UIViewContentMode resizeMode) -{ - // Precompensate for scale - CGFloat scale = sourceScale / destScale; - sourceSize.width *= scale; - sourceSize.height *= scale; - - // Calculate aspect ratios if needed (don't bother is resizeMode == stretch) - CGFloat aspect = 0.0, targetAspect = 0.0; - if (resizeMode != UIViewContentModeScaleToFill) { - aspect = sourceSize.width / sourceSize.height; - targetAspect = destSize.width / destSize.height; - if (aspect == targetAspect) { - resizeMode = UIViewContentModeScaleToFill; - } - } - - switch (resizeMode) { - case UIViewContentModeScaleToFill: // stretch - - sourceSize.width = MIN(destSize.width, sourceSize.width); - sourceSize.height = MIN(destSize.height, sourceSize.height); - return (CGRect){CGPointZero, sourceSize}; - - case UIViewContentModeScaleAspectFit: // contain - - if (targetAspect <= aspect) { // target is taller than content - sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width); - sourceSize.height = sourceSize.width / aspect; - } else { // target is wider than content - sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height); - sourceSize.width = sourceSize.height * aspect; - } - return (CGRect){CGPointZero, sourceSize}; - - case UIViewContentModeScaleAspectFill: // cover - - if (targetAspect <= aspect) { // target is taller than content - - sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height); - sourceSize.width = sourceSize.height * aspect; - destSize.width = destSize.height * targetAspect; - return (CGRect){{(destSize.width - sourceSize.width) / 2, 0}, sourceSize}; - - } else { // target is wider than content - - sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width); - sourceSize.height = sourceSize.width / aspect; - destSize.height = destSize.width / targetAspect; - return (CGRect){{0, (destSize.height - sourceSize.height) / 2}, sourceSize}; - } - - default: - - RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode); - return (CGRect){CGPointZero, destSize}; - } -} diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h index 186a53cd1..4337836fd 100644 --- a/Libraries/Image/RCTImageLoader.h +++ b/Libraries/Image/RCTImageLoader.h @@ -7,20 +7,37 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import +#import @class ALAssetsLibrary; -@class UIImage; @interface RCTImageLoader : NSObject +/** + * The shared asset library instance. + */ + (ALAssetsLibrary *)assetsLibrary; /** * Can be called from any thread. * Will always call callback on main thread. */ -+ (void)loadImageWithTag:(NSString *)tag ++ (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback; +/** + * As above, but includes target size, scale and resizeMode, which are used to + * select the optimal dimensions for the loaded image. + */ ++ (void)loadImageWithTag:(NSString *)imageTag + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(UIViewContentMode)resizeMode + callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback; + +/** + * Is the specified image tag an asset library image? + */ ++ (BOOL)isAssetLibraryImage:(NSString *)imageTag; + @end diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 0e4a9c171..69d98a60a 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -19,6 +19,7 @@ #import "RCTDefines.h" #import "RCTGIFImage.h" #import "RCTImageDownloader.h" +#import "RCTImageUtils.h" #import "RCTLog.h" #import "RCTUtils.h" @@ -56,13 +57,23 @@ static dispatch_queue_t RCTImageLoaderQueue(void) return assetsLibrary; } -/** - * Can be called from any thread. - * Will always call callback on main thread. - */ -+ (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error, id image))callback ++ (void)loadImageWithTag:(NSString *)imageTag + callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback { - if ([imageTag hasPrefix:@"assets-library"]) { + return [self loadImageWithTag:imageTag + size:CGSizeZero + scale:0 + resizeMode:UIViewContentModeScaleToFill + callback:callback]; +} + ++ (void)loadImageWithTag:(NSString *)imageTag + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(UIViewContentMode)resizeMode + callback:(void (^)(NSError *error, id image))callback +{ + if ([imageTag hasPrefix:@"assets-library://"]) { [[RCTImageLoader assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) { if (asset) { // ALAssetLibrary API is async and will be multi-threaded. Loading a few full @@ -73,9 +84,31 @@ static dispatch_queue_t RCTImageLoaderQueue(void) // Also make sure the image is released immediately after it's used so it // doesn't spike the memory up during the process. @autoreleasepool { - ALAssetRepresentation *representation = [asset defaultRepresentation]; - ALAssetOrientation orientation = [representation orientation]; - UIImage *image = [UIImage imageWithCGImage:[representation fullResolutionImage] scale:1.0f orientation:(UIImageOrientation)orientation]; + + BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero); + ALAssetOrientation orientation = ALAssetOrientationUp; + CGImageRef imageRef = NULL; + + if (!useMaximumSize) { + imageRef = asset.thumbnail; + } + if (RCTUpscalingRequired((CGSize){CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}, 1, size, scale, resizeMode)) { + if (!useMaximumSize) { + imageRef = asset.aspectRatioThumbnail; + } + if (RCTUpscalingRequired((CGSize){CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}, 1, size, scale, resizeMode)) { + ALAssetRepresentation *representation = [asset defaultRepresentation]; + orientation = [representation orientation]; + if (!useMaximumSize) { + imageRef = [representation fullScreenImage]; + } + if (RCTUpscalingRequired((CGSize){CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}, 1, size, scale, resizeMode)) { + imageRef = [representation fullResolutionImage]; + } + } + } + + UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale orientation:(UIImageOrientation)orientation]; RCTDispatchCallbackOnMainQueue(callback, nil, image); } }); @@ -91,9 +124,9 @@ static dispatch_queue_t RCTImageLoaderQueue(void) }]; } else if ([imageTag hasPrefix:@"ph://"]) { // Using PhotoKit for iOS 8+ - // 'ph://' prefix is used by FBMediaKit to differentiate between assets-library. It is prepended to the local ID so that it - // is in the form of NSURL which is what assets-library is based on. - // This means if we use any FB standard photo picker, we will get this prefix =( + // The 'ph://' prefix is used by FBMediaKit to differentiate between + // assets-library. It is prepended to the local ID so that it is in the + // form of an, NSURL which is what assets-library uses. NSString *phAssetID = [imageTag substringFromIndex:[@"ph://" length]]; PHFetchResult *results = [PHAsset fetchAssetsWithLocalIdentifiers:@[phAssetID] options:nil]; if (results.count == 0) { @@ -104,7 +137,12 @@ static dispatch_queue_t RCTImageLoaderQueue(void) } PHAsset *asset = [results firstObject]; - [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:nil resultHandler:^(UIImage *result, NSDictionary *info) { + CGSize targetSize = CGSizeEqualToSize(size, CGSizeZero) ? PHImageManagerMaximumSize : size; + PHImageContentMode contentMode = PHImageContentModeAspectFill; + if (resizeMode == UIViewContentModeScaleAspectFit) { + contentMode = PHImageContentModeAspectFit; + } + [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:nil resultHandler:^(UIImage *result, NSDictionary *info) { if (result) { RCTDispatchCallbackOnMainQueue(callback, nil, result); } else { @@ -121,13 +159,20 @@ static dispatch_queue_t RCTImageLoaderQueue(void) RCTDispatchCallbackOnMainQueue(callback, RCTErrorWithMessage(errorMessage), nil); return; } - [[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:nil block:^(NSData *data, NSError *error) { - if (error) { - RCTDispatchCallbackOnMainQueue(callback, error, nil); - } else { - RCTDispatchCallbackOnMainQueue(callback, nil, [UIImage imageWithData:data]); - } - }]; + if ([[imageTag lowercaseString] hasSuffix:@".gif"]) { + [[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:nil block:^(NSData *data, NSError *error) { + id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]); + if (!image && !error) { + NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", imageTag]; + error = RCTErrorWithMessage(errorMessage); + } + RCTDispatchCallbackOnMainQueue(callback, error, image); + }]; + } else { + [[RCTImageDownloader sharedInstance] downloadImageForURL:url size:size scale:scale resizeMode:resizeMode tintColor:nil backgroundColor:nil progressBlock:NULL block:^(UIImage *image, NSError *error) { + RCTDispatchCallbackOnMainQueue(callback, error, image); + }]; + } } else if ([[imageTag lowercaseString] hasSuffix:@".gif"]) { id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]); if (image) { @@ -149,4 +194,9 @@ static dispatch_queue_t RCTImageLoaderQueue(void) } } ++ (BOOL)isAssetLibraryImage:(NSString *)imageTag +{ + return [imageTag hasPrefix:@"assets-library://"] || [imageTag hasPrefix:@"ph:"]; +} + @end diff --git a/Libraries/Image/RCTImageUtils.h b/Libraries/Image/RCTImageUtils.h new file mode 100644 index 000000000..cbb38cda8 --- /dev/null +++ b/Libraries/Image/RCTImageUtils.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#import + +#import "RCTDefines.h" + +/** + * Returns the optimal context size for an image drawn using the clip rect + * returned by RCTClipRect. + */ +RCT_EXTERN CGSize RCTTargetSizeForClipRect(CGRect clipRect); + +/** + * This function takes an input content size & scale (typically from an image), + * a target size & scale that it will be drawn into (typically a CGContext) and + * then calculates the optimal rectangle to draw the image into so that it will + * be sized and positioned correctly if drawn using the specified content mode. + */ +RCT_EXTERN CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, + CGSize destSize, CGFloat destScale, + UIViewContentMode resizeMode); + +/** + * This function takes an input content size & scale (typically from an image), + * a target size & scale that it will be displayed at, and determines if the + * source will need to be upscaled to fit (which may result in pixelization). + */ +RCT_EXTERN BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale, + CGSize destSize, CGFloat destScale, + UIViewContentMode resizeMode); diff --git a/Libraries/Image/RCTImageUtils.m b/Libraries/Image/RCTImageUtils.m new file mode 100644 index 000000000..89d269532 --- /dev/null +++ b/Libraries/Image/RCTImageUtils.m @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTImageUtils.h" + +#import "RCTLog.h" + +CGSize RCTTargetSizeForClipRect(CGRect clipRect) +{ + return (CGSize){ + clipRect.size.width + clipRect.origin.x * 2, + clipRect.size.height + clipRect.origin.y * 2 + }; +} + +CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, + CGSize destSize, CGFloat destScale, + UIViewContentMode resizeMode) +{ + if (CGSizeEqualToSize(destSize, CGSizeZero)) { + // Assume we require the largest size available + return (CGRect){CGPointZero, sourceSize}; + } + + // Precompensate for scale + CGFloat scale = sourceScale / destScale; + sourceSize.width *= scale; + sourceSize.height *= scale; + + // Calculate aspect ratios if needed (don't bother if resizeMode == stretch) + CGFloat aspect = 0.0, targetAspect = 0.0; + if (resizeMode != UIViewContentModeScaleToFill) { + aspect = sourceSize.width / sourceSize.height; + targetAspect = destSize.width / destSize.height; + if (aspect == targetAspect) { + resizeMode = UIViewContentModeScaleToFill; + } + } + + switch (resizeMode) { + case UIViewContentModeScaleToFill: // stretch + + sourceSize.width = MIN(destSize.width, sourceSize.width); + sourceSize.height = MIN(destSize.height, sourceSize.height); + return (CGRect){CGPointZero, sourceSize}; + + case UIViewContentModeScaleAspectFit: // contain + + if (targetAspect <= aspect) { // target is taller than content + + sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width); + sourceSize.height = sourceSize.width / aspect; + + } else { // target is wider than content + + sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height); + sourceSize.width = sourceSize.height * aspect; + } + return (CGRect){CGPointZero, sourceSize}; + + case UIViewContentModeScaleAspectFill: // cover + + if (targetAspect <= aspect) { // target is taller than content + + sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height); + sourceSize.width = sourceSize.height * aspect; + destSize.width = destSize.height * targetAspect; + return (CGRect){{(destSize.width - sourceSize.width) / 2, 0}, sourceSize}; + + } else { // target is wider than content + + sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width); + sourceSize.height = sourceSize.width / aspect; + destSize.height = destSize.width / targetAspect; + return (CGRect){{0, (destSize.height - sourceSize.height) / 2}, sourceSize}; + } + + default: + + RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode); + return (CGRect){CGPointZero, destSize}; + } +} + +RCT_EXTERN BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale, + CGSize destSize, CGFloat destScale, + UIViewContentMode resizeMode) +{ + if (CGSizeEqualToSize(destSize, CGSizeZero)) { + // Assume we require the largest size available + return YES; + } + + // Precompensate for scale + CGFloat scale = sourceScale / destScale; + sourceSize.width *= scale; + sourceSize.height *= scale; + + // Calculate aspect ratios if needed (don't bother if resizeMode == stretch) + CGFloat aspect = 0.0, targetAspect = 0.0; + if (resizeMode != UIViewContentModeScaleToFill) { + aspect = sourceSize.width / sourceSize.height; + targetAspect = destSize.width / destSize.height; + if (aspect == targetAspect) { + resizeMode = UIViewContentModeScaleToFill; + } + } + + switch (resizeMode) { + case UIViewContentModeScaleToFill: // stretch + + return destSize.width > sourceSize.width || destSize.height > sourceSize.height; + + case UIViewContentModeScaleAspectFit: // contain + + if (targetAspect <= aspect) { // target is taller than content + + return destSize.width > sourceSize.width; + + } else { // target is wider than content + + return destSize.height > sourceSize.height; + } + + case UIViewContentModeScaleAspectFill: // cover + + if (targetAspect <= aspect) { // target is taller than content + + return destSize.height > sourceSize.height; + + } else { // target is wider than content + + return destSize.width > sourceSize.width; + } + + default: + + RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode); + return NO; + } +} diff --git a/Libraries/Image/RCTStaticImage.h b/Libraries/Image/RCTStaticImage.h index eb82b597c..c8f46a302 100644 --- a/Libraries/Image/RCTStaticImage.h +++ b/Libraries/Image/RCTStaticImage.h @@ -13,5 +13,6 @@ @property (nonatomic, assign) UIEdgeInsets capInsets; @property (nonatomic, assign) UIImageRenderingMode renderingMode; +@property (nonatomic, copy) NSString *src; @end diff --git a/Libraries/Image/RCTStaticImage.m b/Libraries/Image/RCTStaticImage.m index f9bef7c5d..0e9d4b608 100644 --- a/Libraries/Image/RCTStaticImage.m +++ b/Libraries/Image/RCTStaticImage.m @@ -9,6 +9,13 @@ #import "RCTStaticImage.h" +#import "RCTConvert.h" +#import "RCTGIFImage.h" +#import "RCTImageLoader.h" +#import "RCTUtils.h" + +#import "UIView+React.h" + @implementation RCTStaticImage - (void)_updateImage @@ -59,4 +66,54 @@ } } +- (void)setSrc:(NSString *)src +{ + if (![src isEqual:_src]) { + _src = [src copy]; + [self reloadImage]; + } +} + +- (void)reloadImage +{ + if (_src && !CGSizeEqualToSize(self.frame.size, CGSizeZero)) { + [RCTImageLoader loadImageWithTag:_src + size:self.bounds.size + scale:RCTScreenScale() + resizeMode:self.contentMode callback:^(NSError *error, id image) { + if (error) { + RCTLogWarn(@"%@", error.localizedDescription); + } + if ([image isKindOfClass:[CAAnimation class]]) { + [self.layer addAnimation:image forKey:@"contents"]; + } else { + [self.layer removeAnimationForKey:@"contents"]; + self.image = image; + } + }]; + } else { + [self.layer removeAnimationForKey:@"contents"]; + self.image = nil; + } +} + +- (void)reactSetFrame:(CGRect)frame +{ + [super reactSetFrame:frame]; + if (self.image == nil) { + [self reloadImage]; + } else if ([RCTImageLoader isAssetLibraryImage:_src]) { + CGSize imageSize = { + self.image.size.width / RCTScreenScale(), + self.image.size.height / RCTScreenScale() + }; + CGFloat widthChangeFraction = imageSize.width ? ABS(imageSize.width - frame.size.width) / imageSize.width : 1; + CGFloat heightChangeFraction = imageSize.height ? ABS(imageSize.height - frame.size.height) / imageSize.height : 1; + // If the combined change is more than 20%, reload the asset in case there is a better size. + if (widthChangeFraction + heightChangeFraction > 0.2) { + [self reloadImage]; + } + } +} + @end diff --git a/Libraries/Image/RCTStaticImageManager.m b/Libraries/Image/RCTStaticImageManager.m index bdc6f0596..7b3fb16db 100644 --- a/Libraries/Image/RCTStaticImageManager.m +++ b/Libraries/Image/RCTStaticImageManager.m @@ -12,8 +12,6 @@ #import #import "RCTConvert.h" -#import "RCTGIFImage.h" -#import "RCTImageLoader.h" #import "RCTStaticImage.h" @implementation RCTStaticImageManager @@ -26,21 +24,9 @@ RCT_EXPORT_MODULE() } RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets) +RCT_REMAP_VIEW_PROPERTY(imageTag, src, NSString) RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode) -RCT_CUSTOM_VIEW_PROPERTY(src, NSURL, RCTStaticImage) -{ - if (json) { - if ([[[json description] pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame) { - [view.layer addAnimation:RCTGIFImageWithFileURL([RCTConvert NSURL:json]) forKey:@"contents"]; - } else { - [view.layer removeAnimationForKey:@"contents"]; - view.image = [RCTConvert UIImage:json]; - } - } else { - [view.layer removeAnimationForKey:@"contents"]; - view.image = defaultView.image; - } -} +RCT_EXPORT_VIEW_PROPERTY(src, NSString) RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTStaticImage) { if (json) { @@ -51,24 +37,5 @@ RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTStaticImage) view.tintColor = defaultView.tintColor; } } -RCT_CUSTOM_VIEW_PROPERTY(imageTag, NSString, RCTStaticImage) -{ - if (json) { - [RCTImageLoader loadImageWithTag:[RCTConvert NSString:json] callback:^(NSError *error, id image) { - if (error) { - RCTLogWarn(@"%@", error.localizedDescription); - } - if ([image isKindOfClass:[CAAnimation class]]) { - [view.layer addAnimation:image forKey:@"contents"]; - } else { - [view.layer removeAnimationForKey:@"contents"]; - view.image = image; - } - }]; - } else { - [view.layer removeAnimationForKey:@"contents"]; - view.image = defaultView.image; - } -} @end