Updates from Thu Sep 3rd.

This commit is contained in:
Spencer Ahrens
2015-09-03 12:55:40 -07:00
94 changed files with 7570 additions and 1092 deletions

View File

@@ -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);
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -107,6 +107,8 @@ var styles = StyleSheet.create({
},
});
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS, {
nativeOnly: { onChange: true },
});
module.exports = SliderIOS;

View File

@@ -108,6 +108,8 @@ var styles = StyleSheet.create({
},
});
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS);
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS, {
nativeOnly: { onChange: true }
});
module.exports = SwitchIOS;

View File

@@ -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}

View File

@@ -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}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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: {

View File

@@ -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

View 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

View 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

View 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;
}

View File

@@ -14,6 +14,7 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "RCTAssetsLibraryImageLoader.h"
#import "RCTBridge.h"
#import "RCTImageLoader.h"
#import "RCTLog.h"

View File

@@ -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

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View 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

View 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

View File

@@ -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) {

View File

@@ -35,6 +35,7 @@
_textView = [[UITextView alloc] initWithFrame:self.bounds];
_textView.backgroundColor = [UIColor clearColor];
_textView.scrollsToTop = NO;
_textView.delegate = self;
[self addSubview:_textView];
}