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.
This commit is contained in:
Nick Lockwood 2015-07-14 04:06:17 -07:00
parent a65bbe14d3
commit b34a85f4da
9 changed files with 342 additions and 137 deletions

View File

@ -12,6 +12,7 @@
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; }; 1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; };
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; }; 1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; };
1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1345A8381B26592900583190 /* RCTImageRequestHandler.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 */; }; 137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; };
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; }; 143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.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 = "<group>"; }; 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = "<group>"; };
1345A8371B26592900583190 /* RCTImageRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageRequestHandler.h; sourceTree = "<group>"; }; 1345A8371B26592900583190 /* RCTImageRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageRequestHandler.h; sourceTree = "<group>"; };
1345A8381B26592900583190 /* RCTImageRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageRequestHandler.m; sourceTree = "<group>"; }; 1345A8381B26592900583190 /* RCTImageRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageRequestHandler.m; sourceTree = "<group>"; };
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageUtils.h; sourceTree = "<group>"; };
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtils.m; sourceTree = "<group>"; };
137620331B31C53500677FF0 /* RCTImagePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImagePickerManager.h; sourceTree = "<group>"; }; 137620331B31C53500677FF0 /* RCTImagePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImagePickerManager.h; sourceTree = "<group>"; };
137620341B31C53500677FF0 /* RCTImagePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImagePickerManager.m; sourceTree = "<group>"; }; 137620341B31C53500677FF0 /* RCTImagePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImagePickerManager.m; sourceTree = "<group>"; };
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = "<group>"; }; 143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = "<group>"; };
@ -94,6 +97,8 @@
1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */, 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */,
1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */, 1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */,
1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */, 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */,
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */,
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */,
58B5115E1A9E6B3D00147676 /* Products */, 58B5115E1A9E6B3D00147676 /* Products */,
); );
indentWidth = 2; indentWidth = 2;
@ -175,6 +180,7 @@
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */, 143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */, 03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */,
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */, 1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */,
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@ -10,6 +10,7 @@
#import "RCTImageDownloader.h" #import "RCTImageDownloader.h"
#import "RCTDownloadTaskWrapper.h" #import "RCTDownloadTaskWrapper.h"
#import "RCTImageUtils.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTUtils.h" #import "RCTUtils.h"
@ -195,82 +196,3 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
} }
@end @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};
}
}

View File

@ -7,20 +7,37 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*/ */
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h>
@class ALAssetsLibrary; @class ALAssetsLibrary;
@class UIImage;
@interface RCTImageLoader : NSObject @interface RCTImageLoader : NSObject
/**
* The shared asset library instance.
*/
+ (ALAssetsLibrary *)assetsLibrary; + (ALAssetsLibrary *)assetsLibrary;
/** /**
* Can be called from any thread. * Can be called from any thread.
* Will always call callback on main 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; 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 @end

View File

@ -19,6 +19,7 @@
#import "RCTDefines.h" #import "RCTDefines.h"
#import "RCTGIFImage.h" #import "RCTGIFImage.h"
#import "RCTImageDownloader.h" #import "RCTImageDownloader.h"
#import "RCTImageUtils.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTUtils.h" #import "RCTUtils.h"
@ -56,13 +57,23 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
return assetsLibrary; return assetsLibrary;
} }
/** + (void)loadImageWithTag:(NSString *)imageTag
* Can be called from any thread. callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback
* Will always call callback on main thread.
*/
+ (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error, id 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) { [[RCTImageLoader assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) {
if (asset) { if (asset) {
// ALAssetLibrary API is async and will be multi-threaded. Loading a few full // 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 // Also make sure the image is released immediately after it's used so it
// doesn't spike the memory up during the process. // doesn't spike the memory up during the process.
@autoreleasepool { @autoreleasepool {
ALAssetRepresentation *representation = [asset defaultRepresentation];
ALAssetOrientation orientation = [representation orientation]; BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
UIImage *image = [UIImage imageWithCGImage:[representation fullResolutionImage] scale:1.0f orientation:(UIImageOrientation)orientation]; 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); RCTDispatchCallbackOnMainQueue(callback, nil, image);
} }
}); });
@ -91,9 +124,9 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
}]; }];
} else if ([imageTag hasPrefix:@"ph://"]) { } else if ([imageTag hasPrefix:@"ph://"]) {
// Using PhotoKit for iOS 8+ // 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 // The 'ph://' prefix is used by FBMediaKit to differentiate between
// is in the form of NSURL which is what assets-library is based on. // assets-library. It is prepended to the local ID so that it is in the
// This means if we use any FB standard photo picker, we will get this prefix =( // form of an, NSURL which is what assets-library uses.
NSString *phAssetID = [imageTag substringFromIndex:[@"ph://" length]]; NSString *phAssetID = [imageTag substringFromIndex:[@"ph://" length]];
PHFetchResult *results = [PHAsset fetchAssetsWithLocalIdentifiers:@[phAssetID] options:nil]; PHFetchResult *results = [PHAsset fetchAssetsWithLocalIdentifiers:@[phAssetID] options:nil];
if (results.count == 0) { if (results.count == 0) {
@ -104,7 +137,12 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
} }
PHAsset *asset = [results firstObject]; 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) { if (result) {
RCTDispatchCallbackOnMainQueue(callback, nil, result); RCTDispatchCallbackOnMainQueue(callback, nil, result);
} else { } else {
@ -121,13 +159,20 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
RCTDispatchCallbackOnMainQueue(callback, RCTErrorWithMessage(errorMessage), nil); RCTDispatchCallbackOnMainQueue(callback, RCTErrorWithMessage(errorMessage), nil);
return; return;
} }
[[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:nil block:^(NSData *data, NSError *error) { if ([[imageTag lowercaseString] hasSuffix:@".gif"]) {
if (error) { [[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:nil block:^(NSData *data, NSError *error) {
RCTDispatchCallbackOnMainQueue(callback, error, nil); id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
} else { if (!image && !error) {
RCTDispatchCallbackOnMainQueue(callback, nil, [UIImage imageWithData:data]); 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"]) { } else if ([[imageTag lowercaseString] hasSuffix:@".gif"]) {
id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]); id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
if (image) { 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 @end

View File

@ -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 <UIKit/UIKit.h>
#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);

View File

@ -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;
}
}

View File

@ -13,5 +13,6 @@
@property (nonatomic, assign) UIEdgeInsets capInsets; @property (nonatomic, assign) UIEdgeInsets capInsets;
@property (nonatomic, assign) UIImageRenderingMode renderingMode; @property (nonatomic, assign) UIImageRenderingMode renderingMode;
@property (nonatomic, copy) NSString *src;
@end @end

View File

@ -9,6 +9,13 @@
#import "RCTStaticImage.h" #import "RCTStaticImage.h"
#import "RCTConvert.h"
#import "RCTGIFImage.h"
#import "RCTImageLoader.h"
#import "RCTUtils.h"
#import "UIView+React.h"
@implementation RCTStaticImage @implementation RCTStaticImage
- (void)_updateImage - (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 @end

View File

@ -12,8 +12,6 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "RCTConvert.h" #import "RCTConvert.h"
#import "RCTGIFImage.h"
#import "RCTImageLoader.h"
#import "RCTStaticImage.h" #import "RCTStaticImage.h"
@implementation RCTStaticImageManager @implementation RCTStaticImageManager
@ -26,21 +24,9 @@ RCT_EXPORT_MODULE()
} }
RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets) RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
RCT_REMAP_VIEW_PROPERTY(imageTag, src, NSString)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode) RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
RCT_CUSTOM_VIEW_PROPERTY(src, NSURL, RCTStaticImage) RCT_EXPORT_VIEW_PROPERTY(src, NSString)
{
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_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTStaticImage) RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTStaticImage)
{ {
if (json) { if (json) {
@ -51,24 +37,5 @@ RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTStaticImage)
view.tintColor = defaultView.tintColor; 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 @end