mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-03-26 23:24:06 +08:00
Add pluggable image processing system
This commit is contained in:
@@ -54,6 +54,8 @@
|
||||
3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; };
|
||||
834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; };
|
||||
83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; };
|
||||
8385CEF51B873B5C00C6273E /* RCTImageLoaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */; };
|
||||
8385CF041B87479200C6273E /* RCTImageLoaderHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */; };
|
||||
83A936C81B7E0F08005B9C36 /* RCTConvert_UIColorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */; };
|
||||
D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
@@ -217,6 +219,9 @@
|
||||
357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = "<group>"; };
|
||||
58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = "<group>"; };
|
||||
83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = "<group>"; };
|
||||
8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoaderTests.m; sourceTree = "<group>"; };
|
||||
8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoaderHelpers.m; sourceTree = "<group>"; };
|
||||
8385CF051B8747A000C6273E /* RCTImageLoaderHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTImageLoaderHelpers.h; sourceTree = "<group>"; };
|
||||
83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert_UIColorTests.m; sourceTree = "<group>"; };
|
||||
D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@@ -365,6 +370,9 @@
|
||||
1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */,
|
||||
1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */,
|
||||
1300627E1B59179B0043FE5A /* RCTGzipTests.m */,
|
||||
8385CF051B8747A000C6273E /* RCTImageLoaderHelpers.h */,
|
||||
8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */,
|
||||
8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */,
|
||||
144D21231B2204C5006DB32B /* RCTImageUtilTests.m */,
|
||||
13DB03471B5D2ED500C27245 /* RCTJSONTests.m */,
|
||||
13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */,
|
||||
@@ -811,6 +819,8 @@
|
||||
83A936C81B7E0F08005B9C36 /* RCTConvert_UIColorTests.m in Sources */,
|
||||
13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */,
|
||||
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */,
|
||||
8385CF041B87479200C6273E /* RCTImageLoaderHelpers.m in Sources */,
|
||||
8385CEF51B873B5C00C6273E /* RCTImageLoaderTests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#import "RCTImageLoader.h"
|
||||
|
||||
typedef BOOL (^RCTImageURLLoaderCanLoadImageURLHandler)(NSURL *requestURL);
|
||||
typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(NSURL *imageURL, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler);
|
||||
|
||||
@interface RCTConcreteImageURLLoader : NSObject <RCTImageURLLoader>
|
||||
|
||||
- (instancetype)initWithPriority:(float)priority
|
||||
canLoadImageURLHandler:(RCTImageURLLoaderCanLoadImageURLHandler)canLoadImageURLHandler
|
||||
loadImageURLHandler:(RCTImageURLLoaderLoadImageURLHandler)loadImageURLHandler;
|
||||
|
||||
@end
|
||||
|
||||
typedef BOOL (^RCTImageDecoderCanDecodeImageDataHandler)(NSData *imageData);
|
||||
typedef RCTImageLoaderCancellationBlock (^RCTImageDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler);
|
||||
|
||||
@interface RCTConcreteImageDecoder : NSObject <RCTImageDecoder>
|
||||
|
||||
- (instancetype)initWithPriority:(float)priority
|
||||
canDecodeImageDataHandler:(RCTImageDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler
|
||||
decodeImageDataHandler:(RCTImageDecoderDecodeImageDataHandler)decodeImageDataHandler;
|
||||
|
||||
@end
|
||||
|
||||
#define _RCTDefineImageHandler(SUPERCLASS, CLASS_NAME) \
|
||||
@interface CLASS_NAME : SUPERCLASS @end \
|
||||
@implementation CLASS_NAME RCT_EXPORT_MODULE() @end
|
||||
|
||||
#define RCTDefineImageURLLoader(CLASS_NAME) \
|
||||
_RCTDefineImageHandler(RCTConcreteImageURLLoader, CLASS_NAME)
|
||||
|
||||
#define RCTDefineImageDecoder(CLASS_NAME) \
|
||||
_RCTDefineImageHandler(RCTConcreteImageDecoder, CLASS_NAME)
|
||||
105
Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m
Normal file
105
Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#import "RCTImageLoaderHelpers.h"
|
||||
|
||||
@implementation RCTConcreteImageURLLoader
|
||||
{
|
||||
RCTImageURLLoaderCanLoadImageURLHandler _canLoadImageURLHandler;
|
||||
RCTImageURLLoaderLoadImageURLHandler _loadImageURLHandler;
|
||||
float _priority;
|
||||
}
|
||||
|
||||
+ (NSString *)moduleName
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPriority:(float)priority canLoadImageURLHandler:(RCTImageURLLoaderCanLoadImageURLHandler)canLoadImageURLHandler loadImageURLHandler:(RCTImageURLLoaderLoadImageURLHandler)loadImageURLHandler
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_canLoadImageURLHandler = [canLoadImageURLHandler copy];
|
||||
_loadImageURLHandler = [loadImageURLHandler copy];
|
||||
_priority = priority;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
return _canLoadImageURLHandler(requestURL);
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
return _loadImageURLHandler(imageURL, size, scale, resizeMode, progressHandler, completionHandler);
|
||||
}
|
||||
|
||||
- (float)imageLoaderPriority
|
||||
{
|
||||
return _priority;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTConcreteImageDecoder
|
||||
{
|
||||
RCTImageDecoderCanDecodeImageDataHandler _canDecodeImageDataHandler;
|
||||
RCTImageDecoderDecodeImageDataHandler _decodeImageDataHandler;
|
||||
float _priority;
|
||||
}
|
||||
|
||||
+ (NSString *)moduleName
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPriority:(float)priority canDecodeImageDataHandler:(RCTImageDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler decodeImageDataHandler:(RCTImageDecoderDecodeImageDataHandler)decodeImageDataHandler
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_canDecodeImageDataHandler = [canDecodeImageDataHandler copy];
|
||||
_decodeImageDataHandler = [decodeImageDataHandler copy];
|
||||
_priority = priority;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)canDecodeImageData:(NSData *)imageData
|
||||
{
|
||||
return _canDecodeImageDataHandler(imageData);
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
return _decodeImageDataHandler(imageData, size, scale, resizeMode, completionHandler);
|
||||
}
|
||||
|
||||
- (float)imageDecoderPriority
|
||||
{
|
||||
return _priority;
|
||||
}
|
||||
|
||||
@end
|
||||
148
Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m
Normal file
148
Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTImageLoaderHelpers.h"
|
||||
|
||||
unsigned char blackGIF[] = {
|
||||
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x2c, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b
|
||||
};
|
||||
|
||||
RCTDefineImageURLLoader(RCTImageLoaderTestsURLLoader1)
|
||||
RCTDefineImageURLLoader(RCTImageLoaderTestsURLLoader2)
|
||||
RCTDefineImageDecoder(RCTImageLoaderTestsDecoder1)
|
||||
RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
|
||||
|
||||
@interface RCTImageLoaderTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTImageLoaderTests
|
||||
|
||||
- (void)testImageLoading
|
||||
{
|
||||
UIImage *image = [UIImage new];
|
||||
|
||||
id<RCTImageURLLoader> loader = [[RCTImageLoaderTestsURLLoader1 alloc] initWithPriority:1.0 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
|
||||
return YES;
|
||||
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
|
||||
progressHandler(1, 1);
|
||||
completionHandler(nil, image);
|
||||
return nil;
|
||||
}];
|
||||
|
||||
RCTImageLoader *imageLoader = [RCTImageLoader new];
|
||||
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader, imageLoader]; } launchOptions:nil];
|
||||
|
||||
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:UIViewContentModeScaleAspectFit progressBlock:^(int64_t progress, int64_t total) {
|
||||
XCTAssertEqual(progress, 1);
|
||||
XCTAssertEqual(total, 1);
|
||||
} completionBlock:^(NSError *loadError, id loadedImage) {
|
||||
XCTAssertEqualObjects(loadedImage, image);
|
||||
XCTAssertNil(loadError);
|
||||
}];
|
||||
XCTAssertNil(cancelBlock);
|
||||
}
|
||||
|
||||
- (void)testImageLoaderUsesImageURLLoaderWithHighestPriority
|
||||
{
|
||||
UIImage *image = [UIImage new];
|
||||
|
||||
id<RCTImageURLLoader> loader1 = [[RCTImageLoaderTestsURLLoader1 alloc] initWithPriority:1.0 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
|
||||
return YES;
|
||||
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
|
||||
progressHandler(1, 1);
|
||||
completionHandler(nil, image);
|
||||
return nil;
|
||||
}];
|
||||
|
||||
id<RCTImageURLLoader> loader2 = [[RCTImageLoaderTestsURLLoader2 alloc] initWithPriority:0.5 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
|
||||
return YES;
|
||||
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderProgressBlock progressHandler, __unused RCTImageLoaderCompletionBlock completionHandler) {
|
||||
XCTFail(@"Should not have used loader2");
|
||||
return nil;
|
||||
}];
|
||||
|
||||
RCTImageLoader *imageLoader = [RCTImageLoader new];
|
||||
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2, imageLoader]; } launchOptions:nil];
|
||||
|
||||
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:UIViewContentModeScaleAspectFit progressBlock:^(int64_t progress, int64_t total) {
|
||||
XCTAssertEqual(progress, 1);
|
||||
XCTAssertEqual(total, 1);
|
||||
} completionBlock:^(NSError *loadError, id loadedImage) {
|
||||
XCTAssertEqualObjects(loadedImage, image);
|
||||
XCTAssertNil(loadError);
|
||||
}];
|
||||
XCTAssertNil(cancelBlock);
|
||||
}
|
||||
|
||||
- (void)testImageDecoding
|
||||
{
|
||||
NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO];
|
||||
UIImage *image = [[UIImage alloc] initWithData:data];
|
||||
|
||||
id<RCTImageDecoder> decoder = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
|
||||
return YES;
|
||||
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
|
||||
XCTAssertEqualObjects(imageData, data);
|
||||
completionHandler(nil, image);
|
||||
return nil;
|
||||
}];
|
||||
|
||||
RCTImageLoader *imageLoader = [RCTImageLoader new];
|
||||
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder, imageLoader]; } launchOptions:nil];
|
||||
|
||||
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:UIViewContentModeScaleToFill completionBlock:^(NSError *decodeError, id decodedImage) {
|
||||
XCTAssertEqualObjects(decodedImage, image);
|
||||
XCTAssertNil(decodeError);
|
||||
}];
|
||||
XCTAssertNil(cancelBlock);
|
||||
}
|
||||
|
||||
- (void)testImageLoaderUsesImageDecoderWithHighestPriority
|
||||
{
|
||||
NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO];
|
||||
UIImage *image = [[UIImage alloc] initWithData:data];
|
||||
|
||||
id<RCTImageDecoder> decoder1 = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
|
||||
return YES;
|
||||
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
|
||||
XCTAssertEqualObjects(imageData, data);
|
||||
completionHandler(nil, image);
|
||||
return nil;
|
||||
}];
|
||||
|
||||
id<RCTImageDecoder> decoder2 = [[RCTImageLoaderTestsDecoder2 alloc] initWithPriority:0.5 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
|
||||
return YES;
|
||||
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(__unused NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderCompletionBlock completionHandler) {
|
||||
XCTFail(@"Should not have used decoder2");
|
||||
return nil;
|
||||
}];
|
||||
|
||||
RCTImageLoader *imageLoader = [RCTImageLoader new];
|
||||
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2, imageLoader]; } launchOptions:nil];
|
||||
|
||||
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:UIViewContentModeScaleToFill completionBlock:^(NSError *decodeError, id decodedImage) {
|
||||
XCTAssertEqualObjects(decodedImage, image);
|
||||
XCTAssertNil(decodeError);
|
||||
}];
|
||||
XCTAssertNil(cancelBlock);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -7,8 +7,8 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTURLRequestHandler.h"
|
||||
#import "RCTImageLoader.h"
|
||||
|
||||
@interface RCTImageRequestHandler : NSObject <RCTURLRequestHandler>
|
||||
@interface RCTAssetBundleImageLoader : NSObject <RCTImageURLLoader>
|
||||
|
||||
@end
|
||||
80
Libraries/Image/RCTAssetBundleImageLoader.m
Normal file
80
Libraries/Image/RCTAssetBundleImageLoader.m
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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 "RCTAssetBundleImageLoader.h"
|
||||
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTAssetBundleImageLoader
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (NSString *)imageNameForRequestURL:(NSURL *)requestURL
|
||||
{
|
||||
if (!requestURL.fileURL) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *resourcesPath = [NSBundle mainBundle].resourcePath;
|
||||
NSString *requestPath = requestURL.absoluteURL.path;
|
||||
if (requestPath.length < resourcesPath.length + 1) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [requestPath substringFromIndex:resourcesPath.length + 1];
|
||||
}
|
||||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
NSString *imageName = [self imageNameForRequestURL:requestURL];
|
||||
if (!imageName.length) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([[NSBundle mainBundle] URLForResource:imageName withExtension:nil] ||
|
||||
[[NSBundle mainBundle] URLForResource:imageName withExtension:@"png"]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return imageName.pathComponents.count == 1 && !imageName.pathExtension.length;
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
NSString *imageName = [self imageNameForRequestURL:imageURL];
|
||||
|
||||
__block BOOL cancelled = NO;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIImage *image = [UIImage imageNamed:imageName];
|
||||
if (image) {
|
||||
if (progressHandler) {
|
||||
progressHandler(1, 1);
|
||||
}
|
||||
|
||||
if (completionHandler) {
|
||||
completionHandler(nil, image);
|
||||
}
|
||||
} else {
|
||||
if (completionHandler) {
|
||||
NSString *message = [NSString stringWithFormat:@"Could not find image named %@", imageName];
|
||||
completionHandler(RCTErrorWithMessage(message), nil);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ^{
|
||||
cancelled = YES;
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
28
Libraries/Image/RCTAssetsLibraryImageLoader.h
Normal file
28
Libraries/Image/RCTAssetsLibraryImageLoader.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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 "RCTImageLoader.h"
|
||||
|
||||
@interface RCTAssetsLibraryImageLoader : NSObject <RCTImageURLLoader>
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTBridge (RCTAssetsLibraryImageLoader)
|
||||
|
||||
/**
|
||||
* The shared Assets Library image loader
|
||||
*/
|
||||
@property (nonatomic, readonly) RCTAssetsLibraryImageLoader *assetsLibraryImageLoader;
|
||||
|
||||
/**
|
||||
* The shared asset library instance.
|
||||
*/
|
||||
@property (nonatomic, readonly) ALAssetsLibrary *assetsLibrary;
|
||||
|
||||
@end
|
||||
155
Libraries/Image/RCTAssetsLibraryImageLoader.m
Normal file
155
Libraries/Image/RCTAssetsLibraryImageLoader.m
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* 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 "RCTAssetsLibraryImageLoader.h"
|
||||
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTImageUtils.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void);
|
||||
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, CGSize size, CGFloat scale, UIViewContentMode resizeMode, NSError **error);
|
||||
|
||||
@implementation RCTAssetsLibraryImageLoader
|
||||
{
|
||||
ALAssetsLibrary *_assetsLibrary;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
- (ALAssetsLibrary *)assetsLibrary
|
||||
{
|
||||
return _assetsLibrary ?: (_assetsLibrary = [ALAssetsLibrary new]);
|
||||
}
|
||||
|
||||
#pragma mark - RCTImageLoader
|
||||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
return [requestURL.scheme.lowercaseString isEqualToString:@"assets-library"];
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
[[self assetsLibrary] assetForURL:imageURL 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(RCTAssetsLibraryImageLoaderQueue(), ^{
|
||||
// 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);
|
||||
ALAssetRepresentation *representation = [asset defaultRepresentation];
|
||||
|
||||
UIImage *image;
|
||||
NSError *error = nil;
|
||||
if (useMaximumSize) {
|
||||
image = [UIImage imageWithCGImage:representation.fullResolutionImage
|
||||
scale:scale
|
||||
orientation:(UIImageOrientation)representation.orientation];
|
||||
} else {
|
||||
image = RCTScaledImageForAsset(representation, size, scale, resizeMode, &error);
|
||||
}
|
||||
|
||||
completionHandler(error, image);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageURL];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
completionHandler(error, nil);
|
||||
}
|
||||
} failureBlock:^(NSError *loadError) {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageURL, loadError];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
completionHandler(error, nil);
|
||||
}];
|
||||
|
||||
return ^{};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBridge (RCTAssetsLibraryImageLoader)
|
||||
|
||||
- (RCTAssetsLibraryImageLoader *)assetsLibraryImageLoader
|
||||
{
|
||||
return self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])];
|
||||
}
|
||||
|
||||
- (ALAssetsLibrary *)assetsLibrary
|
||||
{
|
||||
return [self.assetsLibraryImageLoader assetsLibrary];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void)
|
||||
{
|
||||
static dispatch_queue_t queue;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
queue = dispatch_queue_create("com.facebook.RCTAssetsLibraryImageLoader", DISPATCH_QUEUE_SERIAL);
|
||||
});
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
// Why use a custom scaling method? Greater efficiency, reduced memory overhead:
|
||||
// http://www.mindsea.com/2012/12/downscaling-huge-alassets-without-fear-of-sigkill
|
||||
|
||||
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, CGSize size, CGFloat scale, UIViewContentMode resizeMode, NSError **error)
|
||||
{
|
||||
NSUInteger length = (NSUInteger)representation.size;
|
||||
NSMutableData *data = [NSMutableData dataWithLength:length];
|
||||
if (![representation getBytes:data.mutableBytes
|
||||
fromOffset:0
|
||||
length:length
|
||||
error:error]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
CGSize sourceSize = representation.dimensions;
|
||||
CGSize targetSize = RCTTargetSize(sourceSize, representation.scale,
|
||||
size, scale, resizeMode, NO);
|
||||
|
||||
NSDictionary *options = @{
|
||||
(id)kCGImageSourceShouldAllowFloat: @YES,
|
||||
(id)kCGImageSourceCreateThumbnailWithTransform: @YES,
|
||||
(id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
|
||||
(id)kCGImageSourceThumbnailMaxPixelSize: @(MAX(targetSize.width, targetSize.height) * scale)
|
||||
};
|
||||
|
||||
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
|
||||
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
|
||||
if (sourceRef) {
|
||||
CFRelease(sourceRef);
|
||||
}
|
||||
|
||||
if (imageRef) {
|
||||
UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale
|
||||
orientation:UIImageOrientationUp];
|
||||
CGImageRelease(imageRef);
|
||||
return image;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTAssetsLibraryImageLoader.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import "RCTImageLoader.h"
|
||||
|
||||
extern CAKeyframeAnimation *RCTGIFImageWithData(NSData *data);
|
||||
extern CAKeyframeAnimation *RCTGIFImageWithFileURL(NSURL *URL);
|
||||
@interface RCTGIFImageDecoder : NSObject <RCTImageDecoder>
|
||||
|
||||
@end
|
||||
@@ -7,16 +7,29 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTGIFImage.h"
|
||||
#import "RCTGIFImageDecoder.h"
|
||||
|
||||
#import "RCTLog.h"
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
static CAKeyframeAnimation *RCTGIFImageWithImageSource(CGImageSourceRef imageSource)
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTGIFImageDecoder
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (BOOL)canDecodeImageData:(NSData *)imageData
|
||||
{
|
||||
if (!UTTypeConformsTo(CGImageSourceGetType(imageSource), kUTTypeGIF)) {
|
||||
return nil;
|
||||
}
|
||||
char header[7] = {};
|
||||
[imageData getBytes:header length:6];
|
||||
|
||||
return !strcmp(header, "GIF87a") == 0 || !strcmp(header, "GIF89a");
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
|
||||
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(imageSource, NULL);
|
||||
NSUInteger loopCount = [properties[(id)kCGImagePropertyGIFDictionary][(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
|
||||
|
||||
@@ -48,6 +61,7 @@ static CAKeyframeAnimation *RCTGIFImageWithImageSource(CGImageSourceRef imageSou
|
||||
delays[i] = delayTime;
|
||||
images[i] = (__bridge_transfer id)image;
|
||||
}
|
||||
CFRelease(imageSource);
|
||||
|
||||
NSMutableArray *keyTimes = [NSMutableArray arrayWithCapacity:delays.count];
|
||||
NSTimeInterval runningDuration = 0;
|
||||
@@ -64,34 +78,9 @@ static CAKeyframeAnimation *RCTGIFImageWithImageSource(CGImageSourceRef imageSou
|
||||
animation.keyTimes = keyTimes;
|
||||
animation.values = images;
|
||||
animation.duration = duration;
|
||||
return animation;
|
||||
completionHandler(nil, animation);
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
CAKeyframeAnimation *RCTGIFImageWithData(NSData *data)
|
||||
{
|
||||
if (data.length == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)data, NULL);
|
||||
CAKeyframeAnimation *animation = RCTGIFImageWithImageSource(imageSource);
|
||||
CFRelease(imageSource);
|
||||
return animation;
|
||||
}
|
||||
|
||||
CAKeyframeAnimation *RCTGIFImageWithFileURL(NSURL *URL)
|
||||
{
|
||||
if (!URL) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (!URL.fileURL) {
|
||||
RCTLogError(@"Loading remote image URLs synchronously is a really bad idea.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)URL, NULL);
|
||||
CAKeyframeAnimation *animation = RCTGIFImageWithImageSource(imageSource);
|
||||
CFRelease(imageSource);
|
||||
return animation;
|
||||
}
|
||||
@end
|
||||
@@ -9,8 +9,7 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTImageView.m */; };
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */; };
|
||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; };
|
||||
1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1345A8381B26592900583190 /* RCTImageRequestHandler.m */; };
|
||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.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 */; };
|
||||
@@ -18,6 +17,9 @@
|
||||
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
|
||||
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.m */; };
|
||||
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
|
||||
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */; };
|
||||
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */; };
|
||||
83DDA1571B8DCA5800892A1C /* RCTAssetBundleImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -37,10 +39,8 @@
|
||||
1304D5A81AA8C4A30002E2BE /* RCTImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageView.m; sourceTree = "<group>"; };
|
||||
1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageViewManager.h; sourceTree = "<group>"; };
|
||||
1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageViewManager.m; sourceTree = "<group>"; };
|
||||
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTGIFImage.h; 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>"; };
|
||||
1345A8381B26592900583190 /* RCTImageRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageRequestHandler.m; sourceTree = "<group>"; };
|
||||
1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTGIFImageDecoder.h; sourceTree = "<group>"; };
|
||||
1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImageDecoder.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>"; };
|
||||
@@ -56,6 +56,12 @@
|
||||
58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = "<group>"; };
|
||||
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
|
||||
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetsLibraryImageLoader.h; sourceTree = "<group>"; };
|
||||
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetsLibraryImageLoader.m; sourceTree = "<group>"; };
|
||||
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPhotoLibraryImageLoader.h; sourceTree = "<group>"; };
|
||||
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPhotoLibraryImageLoader.m; sourceTree = "<group>"; };
|
||||
83DDA1551B8DCA5800892A1C /* RCTAssetBundleImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetBundleImageLoader.h; sourceTree = "<group>"; };
|
||||
83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetBundleImageLoader.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -72,10 +78,14 @@
|
||||
58B511541A9E6B3D00147676 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
83DDA1551B8DCA5800892A1C /* RCTAssetBundleImageLoader.h */,
|
||||
83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */,
|
||||
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */,
|
||||
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */,
|
||||
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */,
|
||||
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */,
|
||||
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */,
|
||||
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */,
|
||||
1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */,
|
||||
1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */,
|
||||
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
|
||||
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */,
|
||||
354631661B69857700AA0B86 /* RCTImageEditingManager.h */,
|
||||
@@ -84,8 +94,6 @@
|
||||
143879371AAD32A300F088A5 /* RCTImageLoader.m */,
|
||||
137620331B31C53500677FF0 /* RCTImagePickerManager.h */,
|
||||
137620341B31C53500677FF0 /* RCTImagePickerManager.m */,
|
||||
1345A8371B26592900583190 /* RCTImageRequestHandler.h */,
|
||||
1345A8381B26592900583190 /* RCTImageRequestHandler.m */,
|
||||
1304D5A71AA8C4A30002E2BE /* RCTImageView.h */,
|
||||
1304D5A81AA8C4A30002E2BE /* RCTImageView.m */,
|
||||
1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */,
|
||||
@@ -94,6 +102,8 @@
|
||||
35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */,
|
||||
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */,
|
||||
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */,
|
||||
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */,
|
||||
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */,
|
||||
58B5115E1A9E6B3D00147676 /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
@@ -164,17 +174,19 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */,
|
||||
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */,
|
||||
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */,
|
||||
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
|
||||
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */,
|
||||
1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */,
|
||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */,
|
||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */,
|
||||
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
|
||||
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
|
||||
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */,
|
||||
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,
|
||||
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */,
|
||||
83DDA1571B8DCA5800892A1C /* RCTAssetBundleImageLoader.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -7,25 +7,10 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTImageLoader.h"
|
||||
|
||||
@interface RCTImageDownloader : NSObject <RCTBridgeModule>
|
||||
|
||||
/**
|
||||
* Downloads an image and decompresses it a the size specified. The compressed
|
||||
* image will be cached in memory and to disk. Note that the callback block
|
||||
* will not be executed on the same thread you called the method from, nor on
|
||||
* the main thread. Returns a token that can be used to cancel the download.
|
||||
*/
|
||||
- (RCTImageLoaderCancellationBlock)downloadImageForURL:(NSURL *)url
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)block;
|
||||
@interface RCTImageDownloader : NSObject <RCTImageURLLoader>
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#import "RCTImageDownloader.h"
|
||||
|
||||
#import "RCTGIFImage.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTImageUtils.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTNetworking.h"
|
||||
@@ -25,16 +25,6 @@
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
+ (RCTImageDownloader *)sharedInstance
|
||||
{
|
||||
static RCTImageDownloader *sharedInstance;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [RCTImageDownloader new];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
@@ -44,14 +34,19 @@ RCT_EXPORT_MODULE()
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
return [requestURL.scheme.lowercaseString hasPrefix:@"http"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a block of raw data and returns it. Note that the callback block
|
||||
* will not be executed on the same thread you called the method from, nor on
|
||||
* the main thread. Returns a token that can be used to cancel the download.
|
||||
*/
|
||||
- (RCTImageLoaderCancellationBlock)downloadDataForURL:(NSURL *)url
|
||||
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
|
||||
progressHandler:(RCTImageLoaderProgressBlock)progressBlock
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionBlock
|
||||
{
|
||||
if (![_bridge respondsToSelector:NSSelectorFromString(@"networking")]) {
|
||||
RCTLogError(@"You need to import the RCTNetworking library in order to download remote images.");
|
||||
@@ -99,49 +94,31 @@ RCT_EXPORT_MODULE()
|
||||
return ^{ [task cancel]; };
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)downloadImageForURL:(NSURL *)url
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
scale = scale ?: RCTScreenScale();
|
||||
__block RCTImageLoaderCancellationBlock decodeCancel = nil;
|
||||
|
||||
return [self downloadDataForURL:url progressBlock:progressBlock completionBlock:^(NSError *error, id data) {
|
||||
|
||||
if (!data || error) {
|
||||
completionBlock(error, nil);
|
||||
return;
|
||||
__weak RCTImageDownloader *weakSelf = self;
|
||||
RCTImageLoaderCancellationBlock downloadCancel = [self downloadDataForURL:imageURL progressHandler:progressHandler completionHandler:^(NSError *error, NSData *imageData) {
|
||||
if (error) {
|
||||
completionHandler(error, nil);
|
||||
} else {
|
||||
decodeCancel = [weakSelf.bridge.imageLoader decodeImageData:imageData size:size scale:scale resizeMode:resizeMode completionBlock:completionHandler];
|
||||
}
|
||||
|
||||
if ([url.path.lowercaseString hasSuffix:@".gif"]) {
|
||||
id image = RCTGIFImageWithData(data);
|
||||
if (!image && !error) {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", url];
|
||||
error = RCTErrorWithMessage(errorMessage);
|
||||
}
|
||||
completionBlock(error, image);
|
||||
return;
|
||||
}
|
||||
|
||||
UIImage *image = [UIImage imageWithData:data scale:scale];
|
||||
if (image && !CGSizeEqualToSize(size, CGSizeZero)) {
|
||||
|
||||
// Get destination size
|
||||
CGSize targetSize = RCTTargetSize(image.size, image.scale,
|
||||
size, scale, resizeMode, NO);
|
||||
|
||||
// Decompress image at required size
|
||||
BOOL opaque = !RCTImageHasAlpha(image.CGImage);
|
||||
UIGraphicsBeginImageContextWithOptions(targetSize, opaque, scale);
|
||||
[image drawInRect:(CGRect){CGPointZero, targetSize}];
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
}
|
||||
|
||||
completionBlock(nil, image);
|
||||
}];
|
||||
|
||||
return ^{
|
||||
downloadCancel();
|
||||
|
||||
if (decodeCancel) {
|
||||
decodeCancel();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,14 +10,16 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTURLRequestHandler.h"
|
||||
|
||||
@class ALAssetsLibrary;
|
||||
|
||||
typedef void (^RCTImageLoaderProgressBlock)(int64_t progress, int64_t total);
|
||||
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id image /* UIImage or CAAnimation */);
|
||||
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id image /* NSData, UIImage, CAAnimation */);
|
||||
typedef void (^RCTImageLoaderCancellationBlock)(void);
|
||||
|
||||
@interface RCTImageLoader : NSObject <RCTBridgeModule>
|
||||
@interface RCTImageLoader : NSObject <RCTBridgeModule, RCTURLRequestHandler>
|
||||
|
||||
/**
|
||||
* Loads the specified image at the highest available resolution.
|
||||
@@ -38,14 +40,14 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
|
||||
|
||||
/**
|
||||
* Is the specified image tag an asset library image?
|
||||
* Finds an appropriate image decoder and passes the target size, scale and
|
||||
* resizeMode for optimal image decoding.
|
||||
*/
|
||||
+ (BOOL)isAssetLibraryImage:(NSString *)imageTag;
|
||||
|
||||
/**
|
||||
* Is the specified image tag a remote image?
|
||||
*/
|
||||
+ (BOOL)isRemoteImage:(NSString *)imageTag;
|
||||
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
|
||||
|
||||
@end
|
||||
|
||||
@@ -56,9 +58,72 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
|
||||
*/
|
||||
@property (nonatomic, readonly) RCTImageLoader *imageLoader;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* The shared asset library instance.
|
||||
* Provides the interface needed to register an image data loader. Image data
|
||||
* loaders are also bridge modules, so should be registered using
|
||||
* RCT_EXPORT_MODULE().
|
||||
*/
|
||||
@property (nonatomic, readonly) ALAssetsLibrary *assetsLibrary;
|
||||
@protocol RCTImageURLLoader <RCTBridgeModule>
|
||||
|
||||
/**
|
||||
* Indicates whether this data loader is capable of processing the specified
|
||||
* request URL. Typically the handler would examine the scheme/protocol of the
|
||||
* URL to determine this.
|
||||
*/
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL;
|
||||
|
||||
/**
|
||||
* Send a network request to load the request URL. The method should call the
|
||||
* progressHandler (if applicable) and the completionHandler when the request
|
||||
* has finished. The method should also return a cancellation block, if
|
||||
* applicable.
|
||||
*/
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* If more than one RCTImageURLLoader responds YES to `-canLoadImageURL:`
|
||||
* then `imageLoaderPriority` is used to determine which one to use. The handler
|
||||
* with the highest priority will be selected. Default priority is zero. If
|
||||
* two or more valid handlers have the same priority, the selection order is
|
||||
* undefined.
|
||||
*/
|
||||
- (float)imageLoaderPriority;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Provides the interface needed to register an image decoder. Image decoders
|
||||
* are also bridge modules, so should be registered using RCT_EXPORT_MODULE().
|
||||
*/
|
||||
@protocol RCTImageDecoder <RCTBridgeModule>
|
||||
|
||||
/**
|
||||
* Indicates whether this handler is capable of decoding the specified data.
|
||||
* Typically the handler would examine some sort of header data to determine
|
||||
* this.
|
||||
*/
|
||||
- (BOOL)canDecodeImageData:(NSData *)imageData;
|
||||
|
||||
/**
|
||||
* Decode an image from the data object. The method should call the
|
||||
* completionHandler when the decoding operation has finished. The method
|
||||
* should also return a cancellation block, if applicable.
|
||||
*/
|
||||
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* If more than one RCTImageDecoder responds YES to `-canDecodeImageData:`
|
||||
* then `imageDecoderPriority` is used to determine which one to use. The
|
||||
* handler with the highest priority will be selected. Default priority is zero.
|
||||
* If two or more valid handlers have the same priority, the selection order is
|
||||
* undefined.
|
||||
*/
|
||||
- (float)imageDecoderPriority;
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,19 +9,11 @@
|
||||
|
||||
#import "RCTImageLoader.h"
|
||||
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
#import <Photos/PHAsset.h>
|
||||
#import <Photos/PHFetchResult.h>
|
||||
#import <Photos/PHImageManager.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTGIFImage.h"
|
||||
#import "RCTImageDownloader.h"
|
||||
#import "RCTImageStoreManager.h"
|
||||
#import "RCTImageUtils.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@@ -36,21 +28,7 @@ static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSEr
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@implementation RCTImageLoader
|
||||
{
|
||||
ALAssetsLibrary *_assetsLibrary;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
@@ -67,56 +45,33 @@ RCT_EXPORT_MODULE()
|
||||
completionBlock:callback];
|
||||
}
|
||||
|
||||
// Why use a custom scaling method? Greater efficiency, reduced memory overhead:
|
||||
// http://www.mindsea.com/2012/12/downscaling-huge-alassets-without-fear-of-sigkill
|
||||
|
||||
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation,
|
||||
CGSize size, CGFloat scale,
|
||||
UIViewContentMode resizeMode,
|
||||
NSError **error)
|
||||
- (id<RCTImageURLLoader>)imageURLLoaderForRequest:(NSURL *)requestURL
|
||||
{
|
||||
NSUInteger length = (NSUInteger)representation.size;
|
||||
NSMutableData *data = [NSMutableData dataWithLength:length];
|
||||
if (![representation getBytes:data.mutableBytes
|
||||
fromOffset:0
|
||||
length:length
|
||||
error:error]) {
|
||||
return nil;
|
||||
NSMutableArray *handlers = [NSMutableArray array];
|
||||
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
|
||||
if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) {
|
||||
if ([(id<RCTImageURLLoader>)module canLoadImageURL:requestURL]) {
|
||||
[handlers addObject:module];
|
||||
}
|
||||
}
|
||||
}
|
||||
[handlers sortUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
|
||||
float priorityA = [a respondsToSelector:@selector(imageLoaderPriority)] ? [a imageLoaderPriority] : 0;
|
||||
float priorityB = [b respondsToSelector:@selector(imageLoaderPriority)] ? [b imageLoaderPriority] : 0;
|
||||
if (priorityA < priorityB) {
|
||||
return NSOrderedAscending;
|
||||
} else if (priorityA > priorityB) {
|
||||
return NSOrderedDescending;
|
||||
} else {
|
||||
RCTLogError(@"The RCTImageLoader %@ and %@ both reported that they can"
|
||||
" handle the load request %@, and have equal priority (%g)."
|
||||
" This could result in non-deterministic behavior.",
|
||||
a, b, requestURL, priorityA);
|
||||
|
||||
CGSize sourceSize = representation.dimensions;
|
||||
CGSize targetSize = RCTTargetSize(sourceSize, representation.scale,
|
||||
size, scale, resizeMode, NO);
|
||||
|
||||
NSDictionary *options = @{
|
||||
(id)kCGImageSourceShouldAllowFloat: @YES,
|
||||
(id)kCGImageSourceCreateThumbnailWithTransform: @YES,
|
||||
(id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
|
||||
(id)kCGImageSourceThumbnailMaxPixelSize: @(MAX(targetSize.width, targetSize.height) * scale)
|
||||
};
|
||||
|
||||
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
|
||||
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
|
||||
if (sourceRef) {
|
||||
CFRelease(sourceRef);
|
||||
}
|
||||
|
||||
if (imageRef) {
|
||||
UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale
|
||||
orientation:UIImageOrientationUp];
|
||||
CGImageRelease(imageRef);
|
||||
return image;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (ALAssetsLibrary *)assetsLibrary
|
||||
{
|
||||
if (!_assetsLibrary) {
|
||||
_assetsLibrary = [ALAssetsLibrary new];
|
||||
}
|
||||
return _assetsLibrary;
|
||||
return NSOrderedSame;
|
||||
}
|
||||
}];
|
||||
return [handlers lastObject];
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
||||
@@ -126,141 +81,122 @@ static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation,
|
||||
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
|
||||
{
|
||||
if ([imageTag hasPrefix:@"assets-library://"]) {
|
||||
[[self assetsLibrary] assetForURL:[RCTConvert NSURL: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 {
|
||||
NSURL *requestURL = [RCTConvert NSURL:imageTag];
|
||||
id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForRequest:requestURL];
|
||||
if (!loadHandler) {
|
||||
RCTLogError(@"No suitable image URL loader found for %@", imageTag);
|
||||
}
|
||||
|
||||
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
|
||||
ALAssetRepresentation *representation = [asset defaultRepresentation];
|
||||
return [loadHandler loadImageForURL:requestURL size:size scale:scale resizeMode:resizeMode progressHandler:progressBlock completionHandler:^(NSError *error, id image) {
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
|
||||
}];
|
||||
}
|
||||
|
||||
UIImage *image;
|
||||
NSError *error = nil;
|
||||
if (useMaximumSize) {
|
||||
image = [UIImage imageWithCGImage:representation.fullResolutionImage
|
||||
scale:scale
|
||||
orientation:(UIImageOrientation)representation.orientation];
|
||||
} else {
|
||||
image = RCTScaledImageForAsset(representation, size, scale, resizeMode, &error);
|
||||
}
|
||||
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
|
||||
- (id<RCTImageDecoder>)imageDecoderForRequest:(NSData *)imageData
|
||||
{
|
||||
NSMutableArray *handlers = [NSMutableArray array];
|
||||
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
|
||||
if ([module conformsToProtocol:@protocol(RCTImageDecoder)]) {
|
||||
if ([(id<RCTImageDecoder>)module canDecodeImageData:imageData]) {
|
||||
[handlers addObject:module];
|
||||
}
|
||||
} failureBlock:^(NSError *loadError) {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageTag, loadError];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
|
||||
}];
|
||||
return ^{};
|
||||
} 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.
|
||||
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];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
|
||||
return ^{};
|
||||
}
|
||||
|
||||
PHAsset *asset = results.firstObject;
|
||||
|
||||
PHImageRequestOptions *imageOptions = [PHImageRequestOptions new];
|
||||
|
||||
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
|
||||
CGSize targetSize;
|
||||
|
||||
if ( useMaximumSize ){
|
||||
targetSize = PHImageManagerMaximumSize;
|
||||
imageOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
|
||||
}
|
||||
[handlers sortUsingComparator:^NSComparisonResult(id<RCTImageDecoder> a, id<RCTImageDecoder> b) {
|
||||
float priorityA = [a respondsToSelector:@selector(imageDecoderPriority)] ? [a imageDecoderPriority] : 0;
|
||||
float priorityB = [b respondsToSelector:@selector(imageDecoderPriority)] ? [b imageDecoderPriority] : 0;
|
||||
if (priorityA < priorityB) {
|
||||
return NSOrderedAscending;
|
||||
} else if (priorityA > priorityB) {
|
||||
return NSOrderedDescending;
|
||||
} else {
|
||||
targetSize = size;
|
||||
imageOptions.resizeMode = PHImageRequestOptionsResizeModeFast;
|
||||
}
|
||||
RCTLogError(@"The RCTImageDecoder %@ and %@ both reported that they can"
|
||||
" handle the decode request <NSData %p; %tu bytes>, and have"
|
||||
" equal priority (%g). This could result in"
|
||||
" non-deterministic behavior.",
|
||||
a, b, imageData, imageData.length, priorityA);
|
||||
|
||||
PHImageContentMode contentMode = PHImageContentModeAspectFill;
|
||||
if (resizeMode == UIViewContentModeScaleAspectFit) {
|
||||
contentMode = PHImageContentModeAspectFit;
|
||||
return NSOrderedSame;
|
||||
}
|
||||
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:imageOptions resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
if (result) {
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, nil, result);
|
||||
} else {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load PHAsset with local identifier %@ with no error message.", phAssetID];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
|
||||
return;
|
||||
}
|
||||
}];
|
||||
return ^{};
|
||||
} else if ([imageTag hasPrefix:@"http"]) {
|
||||
NSURL *url = [RCTConvert NSURL:imageTag];
|
||||
if (!url) {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Invalid URL: %@", imageTag];
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, RCTErrorWithMessage(errorMessage), nil);
|
||||
return ^{};
|
||||
}
|
||||
return [_bridge.imageDownloader downloadImageForURL:url size:size scale:scale resizeMode:resizeMode progressBlock:progressBlock completionBlock:^(NSError *error, id image) {
|
||||
}];
|
||||
return [handlers lastObject];
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
|
||||
{
|
||||
id<RCTImageDecoder> imageDecoder = [self imageDecoderForRequest:data];
|
||||
if (imageDecoder) {
|
||||
return [imageDecoder decodeImageData:data size:size scale:scale resizeMode:resizeMode completionHandler:^(NSError *error, id image) {
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
|
||||
}];
|
||||
} else if ([imageTag hasPrefix:@"rct-image-store://"]) {
|
||||
[_bridge.imageStoreManager getImageForTag:imageTag withBlock:^(UIImage *image) {
|
||||
} else {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
UIImage *image = [UIImage imageWithData:data];
|
||||
if (image) {
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, nil, image);
|
||||
} else {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load image from image store: %@", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorMessage);
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length];
|
||||
NSError *finalError = RCTErrorWithMessage(errorMessage);
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, finalError, nil);
|
||||
}
|
||||
}];
|
||||
return ^{};
|
||||
} else if ([imageTag.lowercaseString hasSuffix:@".gif"]) {
|
||||
id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
|
||||
if (image) {
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, nil, image);
|
||||
} else {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorMessage);
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
|
||||
}
|
||||
return ^{};
|
||||
} else {
|
||||
UIImage *image = [RCTConvert UIImage:imageTag];
|
||||
if (image) {
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, nil, image);
|
||||
} else {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unrecognized tag protocol: %@", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorMessage);
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
|
||||
}
|
||||
});
|
||||
return ^{};
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)isAssetLibraryImage:(NSString *)imageTag
|
||||
#pragma mark - RCTURLRequestHandler
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
return [imageTag hasPrefix:@"assets-library://"] || [imageTag hasPrefix:@"ph://"];
|
||||
id<RCTImageURLLoader> handler = [self imageURLLoaderForRequest:request.URL];
|
||||
|
||||
// RCTImageDownloader is an image plugin that uses the networking stack.
|
||||
// We don't want to route any network calls through the image downloader
|
||||
// as that would cause cyclical dependencies.
|
||||
return handler && ![handler isKindOfClass:[RCTImageDownloader class]];
|
||||
}
|
||||
|
||||
+ (BOOL)isRemoteImage:(NSString *)imageTag
|
||||
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
return [imageTag hasPrefix:@"http://"] || [imageTag hasPrefix:@"https://"];
|
||||
__block RCTImageLoaderCancellationBlock requestToken;
|
||||
requestToken = [self.bridge.imageLoader loadImageWithTag:request.URL.absoluteString callback:^(NSError *error, UIImage *image) {
|
||||
if (error) {
|
||||
[delegate URLRequest:requestToken didCompleteWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *mimeType = nil;
|
||||
NSData *imageData = nil;
|
||||
if (RCTImageHasAlpha(image.CGImage)) {
|
||||
mimeType = @"image/png";
|
||||
imageData = UIImagePNGRepresentation(image);
|
||||
} else {
|
||||
mimeType = @"image/jpeg";
|
||||
imageData = UIImageJPEGRepresentation(image, 1.0);
|
||||
}
|
||||
|
||||
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
|
||||
MIMEType:mimeType
|
||||
expectedContentLength:imageData.length
|
||||
textEncodingName:nil];
|
||||
|
||||
[delegate URLRequest:requestToken didReceiveResponse:response];
|
||||
[delegate URLRequest:requestToken didReceiveData:imageData];
|
||||
[delegate URLRequest:requestToken didCompleteWithError:nil];
|
||||
}];
|
||||
|
||||
return requestToken;
|
||||
}
|
||||
|
||||
- (void)cancelRequest:(id)requestToken
|
||||
{
|
||||
if (requestToken) {
|
||||
((RCTImageLoaderCancellationBlock)requestToken)();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -272,9 +208,4 @@ static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation,
|
||||
return self.modules[RCTBridgeModuleNameForClass([RCTImageLoader class])];
|
||||
}
|
||||
|
||||
- (ALAssetsLibrary *)assetsLibrary
|
||||
{
|
||||
return [self.imageLoader assetsLibrary];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
//
|
||||
// RCTImageRequestHandler.m
|
||||
// RCTImage
|
||||
//
|
||||
// Created by Nick Lockwood on 09/06/2015.
|
||||
// Copyright (c) 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RCTImageRequestHandler.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTImageRequestHandler
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
return [@[@"assets-library", @"ph"] containsObject:request.URL.scheme.lowercaseString];
|
||||
}
|
||||
|
||||
- (id)sendRequest:(NSURLRequest *)request
|
||||
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
NSString *URLString = request.URL.absoluteString;
|
||||
|
||||
__block RCTImageLoaderCancellationBlock requestToken = nil;
|
||||
requestToken = [_bridge.imageLoader loadImageWithTag:URLString callback:^(NSError *error, UIImage *image) {
|
||||
if (error) {
|
||||
[delegate URLRequest:requestToken didCompleteWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *mimeType = nil;
|
||||
NSData *imageData = nil;
|
||||
if (RCTImageHasAlpha(image.CGImage)) {
|
||||
mimeType = @"image/png";
|
||||
imageData = UIImagePNGRepresentation(image);
|
||||
} else {
|
||||
mimeType = @"image/jpeg";
|
||||
imageData = UIImageJPEGRepresentation(image, 1.0);
|
||||
}
|
||||
|
||||
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
|
||||
MIMEType:mimeType
|
||||
expectedContentLength:imageData.length
|
||||
textEncodingName:nil];
|
||||
|
||||
[delegate URLRequest:requestToken didReceiveResponse:response];
|
||||
[delegate URLRequest:requestToken didReceiveData:imageData];
|
||||
[delegate URLRequest:requestToken didCompleteWithError:nil];
|
||||
}];
|
||||
|
||||
return requestToken;
|
||||
}
|
||||
|
||||
- (void)cancelRequest:(id /* RCTImageLoaderCancellationBlock */)requestToken
|
||||
{
|
||||
if (requestToken) {
|
||||
((RCTImageLoaderCancellationBlock)requestToken)();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -3,9 +3,10 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTURLRequestHandler.h"
|
||||
|
||||
@interface RCTImageStoreManager : NSObject<RCTURLRequestHandler>
|
||||
@interface RCTImageStoreManager : NSObject <RCTImageURLLoader, RCTURLRequestHandler>
|
||||
|
||||
/**
|
||||
* Set and get cached images. These must be called from the main thread.
|
||||
|
||||
@@ -101,7 +101,7 @@ RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
return [@[@"rct-image-store"] containsObject:request.URL.scheme.lowercaseString];
|
||||
return [request.URL.scheme.lowercaseString isEqualToString:@"rct-image-store"];
|
||||
}
|
||||
|
||||
- (id)sendRequest:(NSURLRequest *)request
|
||||
@@ -137,6 +137,29 @@ RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String
|
||||
return request;
|
||||
}
|
||||
|
||||
#pragma mark - RCTImageLoader
|
||||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
return [requestURL.scheme.lowercaseString isEqualToString:@"rct-image-store"];
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
NSString *imageTag = imageURL.absoluteString;
|
||||
[self getImageForTag:imageTag withBlock:^(UIImage *image) {
|
||||
if (image) {
|
||||
completionHandler(nil, image);
|
||||
} else {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load image from image store: %@", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorMessage);
|
||||
completionHandler(error, nil);
|
||||
}
|
||||
}];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBridge (RCTImageStoreManager)
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTGIFImage.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTImageUtils.h"
|
||||
#import "RCTUtils.h"
|
||||
@@ -101,11 +100,20 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)srcNeedsReload:(NSString *)src
|
||||
{
|
||||
return
|
||||
[src hasPrefix:@"http://"] ||
|
||||
[src hasPrefix:@"https://"] ||
|
||||
[src hasPrefix:@"assets-library://"] ||
|
||||
[src hasPrefix:@"ph://"];
|
||||
}
|
||||
|
||||
- (void)setContentMode:(UIViewContentMode)contentMode
|
||||
{
|
||||
if (self.contentMode != contentMode) {
|
||||
super.contentMode = contentMode;
|
||||
if ([RCTImageLoader isAssetLibraryImage:_src] || [RCTImageLoader isRemoteImage:_src]) {
|
||||
if ([RCTImageView srcNeedsReload:_src]) {
|
||||
[self reloadImage];
|
||||
}
|
||||
}
|
||||
@@ -166,7 +174,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
[super reactSetFrame:frame];
|
||||
if (self.image == nil) {
|
||||
[self reloadImage];
|
||||
} else if ([RCTImageLoader isAssetLibraryImage:_src] || [RCTImageLoader isRemoteImage:_src]) {
|
||||
} else if ([RCTImageView srcNeedsReload:_src]) {
|
||||
|
||||
// Get optimal image size
|
||||
CGSize currentSize = self.image.size;
|
||||
|
||||
14
Libraries/Image/RCTPhotoLibraryImageLoader.h
Normal file
14
Libraries/Image/RCTPhotoLibraryImageLoader.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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 "RCTImageLoader.h"
|
||||
|
||||
@interface RCTPhotoLibraryImageLoader : NSObject <RCTImageURLLoader>
|
||||
|
||||
@end
|
||||
82
Libraries/Image/RCTPhotoLibraryImageLoader.m
Normal file
82
Libraries/Image/RCTPhotoLibraryImageLoader.m
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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 "RCTPhotoLibraryImageLoader.h"
|
||||
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
#import "RCTImageUtils.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTPhotoLibraryImageLoader
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
#pragma mark - RCTImageLoader
|
||||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
return [requestURL.scheme.lowercaseString isEqualToString:@"ph"];
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
// 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.
|
||||
NSString *phAssetID = [imageURL.absoluteString 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];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
completionHandler(error, nil);
|
||||
return ^{};
|
||||
}
|
||||
|
||||
PHAsset *asset = [results firstObject];
|
||||
|
||||
PHImageRequestOptions *imageOptions = [PHImageRequestOptions new];
|
||||
imageOptions.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
|
||||
static const double multiplier = 1e6;
|
||||
progressHandler(progress * multiplier, multiplier);
|
||||
};
|
||||
|
||||
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
|
||||
CGSize targetSize;
|
||||
|
||||
if (useMaximumSize) {
|
||||
targetSize = PHImageManagerMaximumSize;
|
||||
imageOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
|
||||
} else {
|
||||
targetSize = size;
|
||||
imageOptions.resizeMode = PHImageRequestOptionsResizeModeFast;
|
||||
}
|
||||
|
||||
PHImageContentMode contentMode = PHImageContentModeAspectFill;
|
||||
if (resizeMode == UIViewContentModeScaleAspectFit) {
|
||||
contentMode = PHImageContentModeAspectFit;
|
||||
}
|
||||
|
||||
PHImageRequestID requestID = [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:imageOptions resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
if (result) {
|
||||
completionHandler(nil, result);
|
||||
} else {
|
||||
completionHandler(info[PHImageErrorKey], nil);
|
||||
}
|
||||
}];
|
||||
|
||||
return ^{
|
||||
[[PHImageManager defaultManager] cancelImageRequest:requestID];
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user