react-native/Libraries/Image/RCTImageLoader.m

214 lines
8.8 KiB
Mathematica
Raw Normal View History

/**
* 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.
*/
2015-03-10 00:08:01 +00:00
#import "RCTImageLoader.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <Photos/PHAsset.h>
#import <Photos/PHFetchResult.h>
#import <Photos/PHImageManager.h>
#import <UIKit/UIKit.h>
#import "RCTConvert.h"
#import "RCTDefines.h"
#import "RCTGIFImage.h"
2015-03-10 00:08:01 +00:00
#import "RCTImageDownloader.h"
#import "RCTImageUtils.h"
2015-03-10 00:08:01 +00:00
#import "RCTLog.h"
#import "RCTUtils.h"
2015-03-10 00:08:01 +00:00
static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSError *error, UIImage *image)
{
if ([NSThread isMainThread]) {
callback(error, image);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
callback(error, image);
});
}
}
static dispatch_queue_t RCTImageLoaderQueue(void)
{
static dispatch_queue_t queue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("com.facebook.rctImageLoader", DISPATCH_QUEUE_SERIAL);
});
return queue;
}
2015-03-10 00:08:01 +00:00
@implementation RCTImageLoader
+ (ALAssetsLibrary *)assetsLibrary
{
static ALAssetsLibrary *assetsLibrary = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
assetsLibrary = [[ALAssetsLibrary alloc] init];
});
return assetsLibrary;
}
+ (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
callback:(RCTImageLoaderCompletionBlock)callback
{
return [self loadImageWithTag:imageTag
size:CGSizeZero
scale:0
resizeMode:UIViewContentModeScaleToFill
progressBlock:nil
completionBlock:callback];
}
+ (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progress
completionBlock:(RCTImageLoaderCompletionBlock)completion
2015-03-10 00:08:01 +00:00
{
if ([imageTag hasPrefix:@"assets-library://"]) {
2015-03-10 00:08:01 +00:00
[[RCTImageLoader assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) {
if (asset) {
// ALAssetLibrary API is async and will be multi-threaded. Loading a few full
// resolution images at once will spike the memory up to store the image data,
// and might trigger memory warnings and/or OOM crashes.
// To improve this, process the loaded asset in a serial queue.
dispatch_async(RCTImageLoaderQueue(), ^{
// Also make sure the image is released immediately after it's used so it
// doesn't spike the memory up during the process.
@autoreleasepool {
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(completion, nil, image);
}
});
2015-03-10 00:08:01 +00:00
} else {
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageTag];
2015-05-11 20:08:39 +00:00
NSError *error = RCTErrorWithMessage(errorText);
RCTDispatchCallbackOnMainQueue(completion, error, nil);
2015-03-10 00:08:01 +00:00
}
} failureBlock:^(NSError *loadError) {
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageTag, loadError];
2015-05-11 20:08:39 +00:00
NSError *error = RCTErrorWithMessage(errorText);
RCTDispatchCallbackOnMainQueue(completion, error, nil);
2015-03-10 00:08:01 +00:00
}];
return ^{};
2015-03-10 00:08:01 +00:00
} else if ([imageTag hasPrefix:@"ph://"]) {
// Using PhotoKit for iOS 8+
// 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.
2015-03-10 00:08:01 +00:00
NSString *phAssetID = [imageTag substringFromIndex:[@"ph://" length]];
PHFetchResult *results = [PHAsset fetchAssetsWithLocalIdentifiers:@[phAssetID] options:nil];
if (results.count == 0) {
NSString *errorText = [NSString stringWithFormat:@"Failed to fetch PHAsset with local identifier %@ with no error message.", phAssetID];
2015-05-11 20:08:39 +00:00
NSError *error = RCTErrorWithMessage(errorText);
RCTDispatchCallbackOnMainQueue(completion, error, nil);
return ^{};
2015-03-10 00:08:01 +00:00
}
PHAsset *asset = [results firstObject];
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) {
2015-03-10 00:08:01 +00:00
if (result) {
RCTDispatchCallbackOnMainQueue(completion, nil, result);
2015-03-10 00:08:01 +00:00
} else {
NSString *errorText = [NSString stringWithFormat:@"Failed to load PHAsset with local identifier %@ with no error message.", phAssetID];
2015-05-11 20:08:39 +00:00
NSError *error = RCTErrorWithMessage(errorText);
RCTDispatchCallbackOnMainQueue(completion, error, nil);
2015-03-10 00:08:01 +00:00
return;
}
}];
return ^{};
2015-03-10 00:08:01 +00:00
} else if ([imageTag hasPrefix:@"http"]) {
NSURL *url = [NSURL URLWithString:imageTag];
if (!url) {
NSString *errorMessage = [NSString stringWithFormat:@"Invalid URL: %@", imageTag];
RCTDispatchCallbackOnMainQueue(completion, RCTErrorWithMessage(errorMessage), nil);
return ^{};
2015-03-10 00:08:01 +00:00
}
if ([imageTag.lowercaseString hasSuffix:@".gif"]) {
return [[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:progress 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(completion, error, image);
}];
} else {
return [[RCTImageDownloader sharedInstance] downloadImageForURL:url size:size scale:scale resizeMode:resizeMode tintColor:nil backgroundColor:nil progressBlock:progress block:^(UIImage *image, NSError *error) {
RCTDispatchCallbackOnMainQueue(completion, error, image);
}];
}
} else if ([imageTag.lowercaseString hasSuffix:@".gif"]) {
id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
if (image) {
RCTDispatchCallbackOnMainQueue(completion, nil, image);
} else {
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", imageTag];
2015-05-11 20:08:39 +00:00
NSError *error = RCTErrorWithMessage(errorMessage);
RCTDispatchCallbackOnMainQueue(completion, error, nil);
}
return ^{};
2015-03-10 00:08:01 +00:00
} else {
UIImage *image = [RCTConvert UIImage:imageTag];
if (image) {
RCTDispatchCallbackOnMainQueue(completion, nil, image);
} else {
NSString *errorMessage = [NSString stringWithFormat:@"Unrecognized tag protocol: %@", imageTag];
2015-05-11 20:08:39 +00:00
NSError *error = RCTErrorWithMessage(errorMessage);
RCTDispatchCallbackOnMainQueue(completion, error, nil);
}
return ^{};
2015-03-10 00:08:01 +00:00
}
}
+ (BOOL)isAssetLibraryImage:(NSString *)imageTag
{
return [imageTag hasPrefix:@"assets-library://"] || [imageTag hasPrefix:@"ph://"];
}
+ (BOOL)isRemoteImage:(NSString *)imageTag
{
return [imageTag hasPrefix:@"http://"] || [imageTag hasPrefix:@"https://"];
}
2015-03-10 00:08:01 +00:00
@end