mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-24 04:16:00 +08:00
Updates from Thu Sep 3rd.
This commit is contained in:
@@ -69,10 +69,7 @@ class Easing {
|
||||
* http://tiny.cc/elastic_b_1 (default bounciness = 1)
|
||||
* http://tiny.cc/elastic_b_3 (bounciness = 3)
|
||||
*/
|
||||
static elastic(bounciness: number): (t: number) => number {
|
||||
if (arguments.length === 0) {
|
||||
bounciness = 1;
|
||||
}
|
||||
static elastic(bounciness: number = 1): (t: number) => number {
|
||||
var p = bounciness * Math.PI;
|
||||
return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p);
|
||||
};
|
||||
|
||||
@@ -30,6 +30,12 @@ var NativeModules = {
|
||||
customBubblingEventTypes: {},
|
||||
customDirectEventTypes: {},
|
||||
Dimensions: {},
|
||||
RCTModalFullscreenView: {
|
||||
Constants: {},
|
||||
},
|
||||
RCTScrollView: {
|
||||
Constants: {},
|
||||
},
|
||||
},
|
||||
AsyncLocalStorage: {
|
||||
getItem: jest.genMockFunction(),
|
||||
@@ -44,6 +50,13 @@ var NativeModules = {
|
||||
appVersion: '0',
|
||||
buildVersion: '0',
|
||||
},
|
||||
ModalFullscreenViewManager: {},
|
||||
AlertManager: {
|
||||
alertWithArgs: jest.genMockFunction(),
|
||||
},
|
||||
Pasteboard: {
|
||||
setPasteboardString: jest.genMockFunction(),
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = NativeModules;
|
||||
|
||||
@@ -277,7 +277,9 @@ if (Platform.OS === 'android') {
|
||||
uiViewClassName: 'RCTMap',
|
||||
});
|
||||
} else {
|
||||
var RCTMap = requireNativeComponent('RCTMap', MapView);
|
||||
var RCTMap = requireNativeComponent('RCTMap', MapView, {
|
||||
nativeOnly: {onChange: true, onPress: true}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = MapView;
|
||||
|
||||
@@ -107,6 +107,8 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
|
||||
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS, {
|
||||
nativeOnly: { onChange: true },
|
||||
});
|
||||
|
||||
module.exports = SliderIOS;
|
||||
|
||||
@@ -108,6 +108,8 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS);
|
||||
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS, {
|
||||
nativeOnly: { onChange: true }
|
||||
});
|
||||
|
||||
module.exports = SwitchIOS;
|
||||
|
||||
@@ -211,6 +211,7 @@ var TouchableHighlight = React.createClass({
|
||||
accessible={true}
|
||||
ref={UNDERLAY_REF}
|
||||
style={this.state.underlayStyle}
|
||||
onLayout={this.props.onLayout}
|
||||
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
|
||||
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
|
||||
onResponderGrant={this.touchableHandleResponderGrant}
|
||||
|
||||
@@ -158,6 +158,7 @@ var TouchableOpacity = React.createClass({
|
||||
accessible={true}
|
||||
style={[this.props.style, {opacity: this.state.anim}]}
|
||||
testID={this.props.testID}
|
||||
onLayout={this.props.onLayout}
|
||||
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
|
||||
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
|
||||
onResponderGrant={this.touchableHandleResponderGrant}
|
||||
|
||||
@@ -44,7 +44,15 @@ var TouchableWithoutFeedback = React.createClass({
|
||||
onPress: React.PropTypes.func,
|
||||
onPressIn: React.PropTypes.func,
|
||||
onPressOut: React.PropTypes.func,
|
||||
/**
|
||||
* Invoked on mount and layout changes with
|
||||
*
|
||||
* `{nativeEvent: {layout: {x, y, width, height}}}`
|
||||
*/
|
||||
onLayout: React.PropTypes.func,
|
||||
|
||||
onLongPress: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Delay in ms, from the start of the touch, before onPressIn is called.
|
||||
*/
|
||||
@@ -113,6 +121,7 @@ var TouchableWithoutFeedback = React.createClass({
|
||||
return (React: any).cloneElement(onlyChild(this.props.children), {
|
||||
accessible: this.props.accessible !== false,
|
||||
testID: this.props.testID,
|
||||
onLayout: this.props.onLayout,
|
||||
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
|
||||
onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
|
||||
onResponderGrant: this.touchableHandleResponderGrant,
|
||||
|
||||
@@ -185,6 +185,10 @@ var View = React.createClass({
|
||||
* Invoked on mount and layout changes with
|
||||
*
|
||||
* {nativeEvent: { layout: {x, y, width, height}}}.
|
||||
*
|
||||
* This event is fired immediately once the layout has been calculated, but
|
||||
* the new layout may not yet be reflected on the screen at the time the
|
||||
* event is received, especially if a layout animation is in progress.
|
||||
*/
|
||||
onLayout: PropTypes.func,
|
||||
|
||||
|
||||
@@ -226,7 +226,13 @@ var WebView = React.createClass({
|
||||
},
|
||||
});
|
||||
|
||||
var RCTWebView = requireNativeComponent('RCTWebView', WebView);
|
||||
var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
|
||||
nativeOnly: {
|
||||
onLoadingStart: true,
|
||||
onLoadingError: true,
|
||||
onLoadingFinish: true,
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
|
||||
@@ -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") || !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,23 @@ RCT_EXPORT_MODULE()
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
// Have to exclude 'file://' from the main bundle, otherwise this would conflict with RCTAssetBundleImageLoader
|
||||
return
|
||||
[requestURL.scheme compare:@"http" options:NSCaseInsensitiveSearch range:NSMakeRange(0, 4)] == NSOrderedSame ||
|
||||
([requestURL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame && ![requestURL.path hasPrefix:[NSBundle mainBundle].resourcePath]) ||
|
||||
[requestURL.scheme caseInsensitiveCompare:@"data"] == NSOrderedSame;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +98,89 @@ 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();
|
||||
if ([imageURL.scheme.lowercaseString hasPrefix:@"http"]) {
|
||||
__block RCTImageLoaderCancellationBlock decodeCancel = nil;
|
||||
|
||||
return [self downloadDataForURL:url progressBlock:progressBlock completionBlock:^(NSError *error, id data) {
|
||||
|
||||
if (!data || error) {
|
||||
completionBlock(error, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
__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];
|
||||
}
|
||||
completionBlock(error, image);
|
||||
return;
|
||||
}
|
||||
}];
|
||||
|
||||
UIImage *image = [UIImage imageWithData:data scale:scale];
|
||||
if (image && !CGSizeEqualToSize(size, CGSizeZero)) {
|
||||
return ^{
|
||||
downloadCancel();
|
||||
|
||||
// Get destination size
|
||||
CGSize targetSize = RCTTargetSize(image.size, image.scale,
|
||||
size, scale, resizeMode, NO);
|
||||
if (decodeCancel) {
|
||||
decodeCancel();
|
||||
}
|
||||
};
|
||||
} else if ([imageURL.scheme caseInsensitiveCompare:@"data"] == NSOrderedSame) {
|
||||
__block BOOL cancelled = NO;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Decompress image at required size
|
||||
BOOL opaque = !RCTImageHasAlpha(image.CGImage);
|
||||
UIGraphicsBeginImageContextWithOptions(targetSize, opaque, scale);
|
||||
[image drawInRect:(CGRect){CGPointZero, targetSize}];
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
}
|
||||
// Normally -dataWithContentsOfURL: would be bad but this is a data URL.
|
||||
NSData *data = [NSData dataWithContentsOfURL:imageURL];
|
||||
|
||||
completionBlock(nil, image);
|
||||
}];
|
||||
UIImage *image = [UIImage imageWithData:data];
|
||||
if (image) {
|
||||
if (progressHandler) {
|
||||
progressHandler(1, 1);
|
||||
}
|
||||
if (completionHandler) {
|
||||
completionHandler(nil, image);
|
||||
}
|
||||
} else {
|
||||
if (completionHandler) {
|
||||
NSString *message = [NSString stringWithFormat:@"Invalid image data for URL: %@", imageURL];
|
||||
completionHandler(RCTErrorWithMessage(message), nil);
|
||||
}
|
||||
}
|
||||
});
|
||||
return ^{
|
||||
cancelled = YES;
|
||||
};
|
||||
} else if ([imageURL.scheme isEqualToString:@"file"]) {
|
||||
__block BOOL cancelled = NO;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:imageURL.resourceSpecifier];
|
||||
if (image) {
|
||||
if (progressHandler) {
|
||||
progressHandler(1, 1);
|
||||
}
|
||||
if (completionHandler) {
|
||||
completionHandler(nil, image);
|
||||
}
|
||||
} else {
|
||||
if (completionHandler) {
|
||||
NSString *message = [NSString stringWithFormat:@"Could not find image at path: %@", imageURL.absoluteString];
|
||||
completionHandler(RCTErrorWithMessage(message), nil);
|
||||
}
|
||||
}
|
||||
});
|
||||
return ^{
|
||||
cancelled = YES;
|
||||
};
|
||||
} else {
|
||||
RCTLogError(@"Unexpected image schema %@", imageURL.scheme);
|
||||
return ^{};
|
||||
}
|
||||
}
|
||||
|
||||
@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,134 @@ 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];
|
||||
|
||||
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);
|
||||
}
|
||||
} 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 ^{};
|
||||
return [loadHandler loadImageForURL:requestURL size:size scale:scale resizeMode:resizeMode progressHandler:^(int64_t progress, int64_t total) {
|
||||
if (!progressBlock) {
|
||||
return;
|
||||
}
|
||||
|
||||
PHAsset *asset = results.firstObject;
|
||||
|
||||
PHImageRequestOptions *imageOptions = [PHImageRequestOptions new];
|
||||
|
||||
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
|
||||
CGSize targetSize;
|
||||
|
||||
if ( useMaximumSize ){
|
||||
targetSize = PHImageManagerMaximumSize;
|
||||
imageOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
|
||||
if ([NSThread isMainThread]) {
|
||||
progressBlock(progress, total);
|
||||
} else {
|
||||
targetSize = size;
|
||||
imageOptions.resizeMode = PHImageRequestOptionsResizeModeFast;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
progressBlock(progress, total);
|
||||
});
|
||||
}
|
||||
} completionHandler:^(NSError *error, id image) {
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
|
||||
}] ?: ^{};
|
||||
}
|
||||
|
||||
PHImageContentMode contentMode = PHImageContentModeAspectFill;
|
||||
if (resizeMode == UIViewContentModeScaleAspectFit) {
|
||||
contentMode = PHImageContentModeAspectFit;
|
||||
}
|
||||
[[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;
|
||||
- (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];
|
||||
}
|
||||
}];
|
||||
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) {
|
||||
}
|
||||
[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 {
|
||||
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);
|
||||
|
||||
return NSOrderedSame;
|
||||
}
|
||||
}];
|
||||
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 +220,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>
|
||||
|
||||
/**
|
||||
* Set and get cached images. These must be called from the main thread.
|
||||
|
||||
@@ -97,44 +97,27 @@ RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - RCTURLRequestHandler
|
||||
#pragma mark - RCTImageLoader
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
return [@[@"rct-image-store"] containsObject:request.URL.scheme.lowercaseString];
|
||||
return [requestURL.scheme.lowercaseString isEqualToString:@"rct-image-store"];
|
||||
}
|
||||
|
||||
- (id)sendRequest:(NSURLRequest *)request
|
||||
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
NSString *imageTag = request.URL.absoluteString;
|
||||
NSString *imageTag = imageURL.absoluteString;
|
||||
[self getImageForTag:imageTag withBlock:^(UIImage *image) {
|
||||
if (!image) {
|
||||
NSError *error = RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag]);
|
||||
[delegate URLRequest:request didCompleteWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *mimeType = nil;
|
||||
NSData *imageData = nil;
|
||||
if (RCTImageHasAlpha(image.CGImage)) {
|
||||
mimeType = @"image/png";
|
||||
imageData = UIImagePNGRepresentation(image);
|
||||
if (image) {
|
||||
completionHandler(nil, image);
|
||||
} else {
|
||||
mimeType = @"image/jpeg";
|
||||
imageData = UIImageJPEGRepresentation(image, 1.0);
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load image from image store: %@", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorMessage);
|
||||
completionHandler(error, nil);
|
||||
}
|
||||
|
||||
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
|
||||
MIMEType:mimeType
|
||||
expectedContentLength:imageData.length
|
||||
textEncodingName:nil];
|
||||
|
||||
[delegate URLRequest:request didReceiveResponse:response];
|
||||
[delegate URLRequest:request didReceiveData:imageData];
|
||||
[delegate URLRequest:request didCompleteWithError:nil];
|
||||
}];
|
||||
return request;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -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"
|
||||
@@ -21,11 +20,11 @@
|
||||
|
||||
@interface RCTImageView ()
|
||||
|
||||
@property (nonatomic, assign) BOOL onLoadStart;
|
||||
@property (nonatomic, assign) BOOL onProgress;
|
||||
@property (nonatomic, assign) BOOL onError;
|
||||
@property (nonatomic, assign) BOOL onLoad;
|
||||
@property (nonatomic, assign) BOOL onLoadEnd;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onLoadStart;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onProgress;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onError;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onLoad;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onLoadEnd;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -116,19 +124,16 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
if (_src && !CGSizeEqualToSize(self.frame.size, CGSizeZero)) {
|
||||
|
||||
if (_onLoadStart) {
|
||||
NSDictionary *event = @{ @"target": self.reactTag };
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"loadStart" body:event];
|
||||
_onLoadStart(nil);
|
||||
}
|
||||
|
||||
RCTImageLoaderProgressBlock progressHandler = nil;
|
||||
if (_onProgress) {
|
||||
progressHandler = ^(int64_t loaded, int64_t total) {
|
||||
NSDictionary *event = @{
|
||||
@"target": self.reactTag,
|
||||
_onProgress(@{
|
||||
@"loaded": @((double)loaded),
|
||||
@"total": @((double)total),
|
||||
};
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"progress" body:event];
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -147,21 +152,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
}
|
||||
if (error) {
|
||||
if (_onError) {
|
||||
NSDictionary *event = @{
|
||||
@"target": self.reactTag,
|
||||
@"error": error.localizedDescription,
|
||||
};
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"error" body:event];
|
||||
_onError(@{ @"error": error.localizedDescription });
|
||||
}
|
||||
} else {
|
||||
if (_onLoad) {
|
||||
NSDictionary *event = @{ @"target": self.reactTag };
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"load" body:event];
|
||||
_onLoad(nil);
|
||||
}
|
||||
}
|
||||
if (_onLoadEnd) {
|
||||
NSDictionary *event = @{ @"target": self.reactTag };
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"loadEnd" body:event];
|
||||
_onLoadEnd(nil);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
@@ -175,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;
|
||||
|
||||
@@ -27,11 +27,11 @@ RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
|
||||
RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
|
||||
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
|
||||
RCT_EXPORT_VIEW_PROPERTY(src, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onLoadStart, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onProgress, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onError, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onLoad, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onLoadStart, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, RCTDirectEventBlock)
|
||||
RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView)
|
||||
{
|
||||
if (json) {
|
||||
@@ -43,15 +43,4 @@ RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView)
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)customDirectEventTypes
|
||||
{
|
||||
return @[
|
||||
@"loadStart",
|
||||
@"progress",
|
||||
@"error",
|
||||
@"load",
|
||||
@"loadEnd",
|
||||
];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
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
|
||||
@@ -30,7 +30,7 @@ var DEVICE_NOTIF_EVENT = 'openURL';
|
||||
*
|
||||
* #### Handling deep links
|
||||
*
|
||||
* If your app was launched from a external url registered to your app you can
|
||||
* If your app was launched from an external url registered to your app you can
|
||||
* access and handle it from any component you want with
|
||||
*
|
||||
* ```
|
||||
@@ -127,7 +127,7 @@ class LinkingIOS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine wether or not the an installed app can handle a given `url`
|
||||
* Determine wether or not an installed app can handle a given `url`
|
||||
* The callback function will be called with `bool supported` as the only argument
|
||||
*/
|
||||
static canOpenURL(url: string, callback: Function) {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
_textView = [[UITextView alloc] initWithFrame:self.bounds];
|
||||
_textView.backgroundColor = [UIColor clearColor];
|
||||
_textView.scrollsToTop = NO;
|
||||
_textView.delegate = self;
|
||||
[self addSubview:_textView];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user