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 */; };
|
||||
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 = "<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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,20 +7,37 @@
|
|||
* 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 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) UIImageRenderingMode renderingMode;
|
||||
@property (nonatomic, copy) NSString *src;
|
||||
|
||||
@end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
|
||||
#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
|
||||
|
|
Loading…
Reference in New Issue