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:
parent
a65bbe14d3
commit
b34a85f4da
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue