diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m index e67edb0b4..8de60a199 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m @@ -49,7 +49,8 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2) NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader]; } launchOptions:nil]; - [bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) { + NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://facebook.github.io/react/img/logo_og.png"]]; + [bridge.imageLoader loadImageWithURLRequest:urlRequest size:CGSizeMake(100, 100) scale:1.0 clipped:YES resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) { XCTAssertEqual(progress, 1); XCTAssertEqual(total, 1); } completionBlock:^(NSError *loadError, id loadedImage) { @@ -79,7 +80,8 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2) NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2]; } launchOptions:nil]; - [bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) { + NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://facebook.github.io/react/img/logo_og.png"]]; + [bridge.imageLoader loadImageWithURLRequest:urlRequest size:CGSizeMake(100, 100) scale:1.0 clipped:YES resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) { XCTAssertEqual(progress, 1); XCTAssertEqual(total, 1); } completionBlock:^(NSError *loadError, id loadedImage) { @@ -103,7 +105,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2) NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder]; } launchOptions:nil]; - RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageDataWithoutClipping:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) { + RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 clipped:NO resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) { XCTAssertEqualObjects(decodedImage, image); XCTAssertNil(decodeError); }]; @@ -132,7 +134,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2) NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2]; } launchOptions:nil]; - RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageDataWithoutClipping:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) { + RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 clipped:NO resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) { XCTAssertEqualObjects(decodedImage, image); XCTAssertNil(decodeError); }]; diff --git a/Libraries/CameraRoll/RCTCameraRollManager.m b/Libraries/CameraRoll/RCTCameraRollManager.m index d060224cc..7d9e82aa2 100644 --- a/Libraries/CameraRoll/RCTCameraRollManager.m +++ b/Libraries/CameraRoll/RCTCameraRollManager.m @@ -82,11 +82,12 @@ RCT_EXPORT_MODULE() NSString *const RCTErrorUnableToLoad = @"E_UNABLE_TO_LOAD"; NSString *const RCTErrorUnableToSave = @"E_UNABLE_TO_SAVE"; -RCT_EXPORT_METHOD(saveImageWithTag:(NSString *)imageTag +RCT_EXPORT_METHOD(saveImageWithTag:(NSURLRequest *)imageRequest resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - [_bridge.imageLoader loadImageWithTag:imageTag callback:^(NSError *loadError, UIImage *loadedImage) { + [_bridge.imageLoader loadImageWithURLRequest:imageRequest + callback:^(NSError *loadError, UIImage *loadedImage) { if (loadError) { reject(RCTErrorUnableToLoad, nil, loadError); return; diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index b2f4718f7..7afec06e8 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -13,6 +13,7 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType'); var ImageResizeMode = require('ImageResizeMode'); +var ImageSourcePropType = require('ImageSourcePropType'); var ImageStylePropTypes = require('ImageStylePropTypes'); var NativeMethodsMixin = require('NativeMethodsMixin'); var NativeModules = require('NativeModules'); @@ -60,24 +61,32 @@ var Image = React.createClass({ propTypes: { style: StyleSheetPropType(ImageStylePropTypes), /** - * `uri` is a string representing the resource identifier for the image, which - * could be an http address, a local file path, or the name of a static image - * resource (which should be wrapped in the `require('./path/to/image.png')` function). + * The image source (either a remote URL or a local file resource). */ - source: PropTypes.oneOfType([ - PropTypes.shape({ - uri: PropTypes.string, - }), - // Opaque type returned by require('./image.jpg') - PropTypes.number, - ]), + source: ImageSourcePropType, /** * A static image to display while loading the image source. * @platform ios */ defaultSource: PropTypes.oneOfType([ PropTypes.shape({ + /** + * `uri` is a string representing the resource identifier for the image, which + * should be either a local file path or the name of a static image resource + * (which should be wrapped in the `require('./path/to/image.png')` function). + */ uri: PropTypes.string, + /** + * `width` and `height` can be specified if known at build time, in which case + * these will be used to set the default `` component dimensions. + */ + width: PropTypes.number, + height: PropTypes.number, + /** + * `scale` is used to indicate the scale factor of the image. Defaults to 1.0 if + * unspecified, meaning that one image pixel equates to one display point / DIP. + */ + scale: PropTypes.number, }), // Opaque type returned by require('./image.jpg') PropTypes.number, @@ -202,7 +211,7 @@ var Image = React.createClass({ }, render: function() { - var source = resolveAssetSource(this.props.source) || {}; + var source = resolveAssetSource(this.props.source) || { uri: null, width: undefined, height: undefined }; var {width, height, uri} = source; var style = flattenStyle([{width, height}, styles.base, this.props.style]) || {}; @@ -216,7 +225,11 @@ var Image = React.createClass({ if (isNetwork && (tintColor || this.props.blurRadius)) { RawImage = RCTImageView; } - + + if (uri === '') { + console.warn('source.uri should not be an empty string'); + } + if (this.props.src) { console.warn('The component requires a `source` property rather than `src`.'); } diff --git a/Libraries/Image/ImageSourcePropType.js b/Libraries/Image/ImageSourcePropType.js new file mode 100644 index 000000000..6f6c7bf8f --- /dev/null +++ b/Libraries/Image/ImageSourcePropType.js @@ -0,0 +1,56 @@ +/** + * 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. + * + * @providesModule ImageSourcePropType + * @no-flow + */ +'use strict'; + +const PropTypes = require('ReactPropTypes'); + +const ImageSourcePropType = PropTypes.oneOfType([ + PropTypes.shape({ + /** + * `uri` is a string representing the resource identifier for the image, which + * could be an http address, a local file path, or the name of a static image + * resource (which should be wrapped in the `require('./path/to/image.png')` + * function). + */ + uri: PropTypes.string, + /** + * `method` is the HTTP Method to use. Defaults to GET if not specified. + */ + method: PropTypes.string, + /** + * `headers` is an object representing the HTTP headers to send along with the + * request for a remote image. + */ + headers: PropTypes.objectOf(PropTypes.string), + /** + * `body` is the HTTP body to send with the request. This must be a valid + * UTF-8 string, and will be sent exactly as specified, with no + * additional encoding (e.g. URL-escaping or base64) applied. + */ + body: PropTypes.string, + /** + * `width` and `height` can be specified if known at build time, in which case + * these will be used to set the default `` component dimensions. + */ + width: PropTypes.number, + height: PropTypes.number, + /** + * `scale` is used to indicate the scale factor of the image. Defaults to 1.0 if + * unspecified, meaning that one image pixel equates to one display point / DIP. + */ + scale: PropTypes.number, + }), + // Opaque type returned by require('./image.jpg') + PropTypes.number, +]); + +module.exports = ImageSourcePropType; diff --git a/Libraries/Image/RCTImageEditingManager.m b/Libraries/Image/RCTImageEditingManager.m index 9ff231d64..cc8dfd0fa 100644 --- a/Libraries/Image/RCTImageEditingManager.m +++ b/Libraries/Image/RCTImageEditingManager.m @@ -28,14 +28,14 @@ RCT_EXPORT_MODULE() /** * Crops an image and adds the result to the image store. * - * @param imageTag A URL, a string identifying an asset etc. + * @param imageRequest An image URL * @param cropData Dictionary with `offset`, `size` and `displaySize`. * `offset` and `size` are relative to the full-resolution image size. * `displaySize` is an optimization - if specified, the image will * be scaled down to `displaySize` rather than `size`. * All units are in px (not points). */ -RCT_EXPORT_METHOD(cropImage:(NSString *)imageTag +RCT_EXPORT_METHOD(cropImage:(NSURLRequest *)imageRequest cropData:(NSDictionary *)cropData successCallback:(RCTResponseSenderBlock)successCallback errorCallback:(RCTResponseErrorBlock)errorCallback) @@ -45,7 +45,7 @@ RCT_EXPORT_METHOD(cropImage:(NSString *)imageTag [RCTConvert CGSize:cropData[@"size"]] }; - [_bridge.imageLoader loadImageWithTag:imageTag callback:^(NSError *error, UIImage *image) { + [_bridge.imageLoader loadImageWithURLRequest:imageRequest callback:^(NSError *error, UIImage *image) { if (error) { errorCallback(error); return; diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h index d11c42410..02d358404 100644 --- a/Libraries/Image/RCTImageLoader.h +++ b/Libraries/Image/RCTImageLoader.h @@ -56,56 +56,85 @@ typedef void (^RCTImageLoaderCancellationBlock)(void); * Loads the specified image at the highest available resolution. * Can be called from any thread, will call back on an unspecified thread. */ -- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag - callback:(RCTImageLoaderCompletionBlock)callback; +- (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest + callback:(RCTImageLoaderCompletionBlock)callback; /** - * As above, but includes target size, scale and resizeMode, which are used to - * select the optimal dimensions for the loaded image. + * As above, but includes target `size`, `scale` and `resizeMode`, which are used to + * select the optimal dimensions for the loaded image. The `clipped` option + * controls whether the image will be clipped to fit the specified size exactly, + * or if the original aspect ratio should be retained. */ -- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - progressBlock:(RCTImageLoaderProgressBlock)progressBlock - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock; +- (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest + size:(CGSize)size + scale:(CGFloat)scale + clipped:(BOOL)clipped + resizeMode:(RCTResizeMode)resizeMode + progressBlock:(RCTImageLoaderProgressBlock)progressBlock + completionBlock:(RCTImageLoaderCompletionBlock)completionBlock; /** - * Loads an image without clipping the result to fit - used by RCTImageView. - */ -- (RCTImageLoaderCancellationBlock)loadImageWithoutClipping:(NSString *)imageTag - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - progressBlock:(RCTImageLoaderProgressBlock)progressBlock - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock; - -/** - * Finds an appropriate image decoder and passes the target size, scale and - * resizeMode for optimal image decoding. Can be called from any thread, - * will call callback on an unspecified thread. + * Finds an appropriate image decoder and passes the target `size`, `scale` and + * `resizeMode` for optimal image decoding. The `clipped` option controls + * whether the image will be clipped to fit the specified size exactly, or + * if the original aspect ratio should be retained. Can be called from any + * thread, will call callback on an unspecified thread. */ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale + clipped:(BOOL)clipped resizeMode:(RCTResizeMode)resizeMode completionBlock:(RCTImageLoaderCompletionBlock)completionBlock; -/** - * Decodes an image without clipping the result to fit. - */ -- (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)data - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock; - /** * Get image size, in pixels. This method will do the least work possible to get * the information, and won't decode the image if it doesn't have to. */ +- (RCTImageLoaderCancellationBlock)getImageSizeForURLRequest:(NSURLRequest *)imageURLRequest + block:(void(^)(NSError *error, CGSize size))completionBlock; + +@end + +@interface RCTImageLoader (Deprecated) + +- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag + callback:(RCTImageLoaderCompletionBlock)callback +__deprecated_msg("Use loadImageWithURLRequest:callback: instead"); + +- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(RCTResizeMode)resizeMode + progressBlock:(RCTImageLoaderProgressBlock)progressBlock + completionBlock:(RCTImageLoaderCompletionBlock)completionBlock +__deprecated_msg("Use loadImageWithURLRequest:size:scale:clipped:resizeMode:progressBlock:completionBlock: instead"); + +- (RCTImageLoaderCancellationBlock)loadImageWithoutClipping:(NSString *)imageTag + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(RCTResizeMode)resizeMode + progressBlock:(RCTImageLoaderProgressBlock)progressBlock + completionBlock:(RCTImageLoaderCompletionBlock)completionBlock +__deprecated_msg("Use loadImageWithURLRequest:size:scale:clipped:resizeMode:progressBlock:completionBlock: instead"); + +- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(RCTResizeMode)resizeMode + completionBlock:(RCTImageLoaderCompletionBlock)completionBlock +__deprecated_msg("Use decodeImageData:size:scale:clipped:resizeMode:completionBlock: instead"); + +- (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)data + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(RCTResizeMode)resizeMode + completionBlock:(RCTImageLoaderCompletionBlock)completionBlock +__deprecated_msg("Use decodeImageData:size:scale:clipped:resizeMode:completionBlock: instead"); + - (RCTImageLoaderCancellationBlock)getImageSize:(NSString *)imageTag - block:(void(^)(NSError *error, CGSize size))completionBlock; + block:(void(^)(NSError *error, CGSize size))completionBlock +__deprecated_msg("Use getImageSizeWithURLRequest:callback: instead"); @end diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index c6f4e2ec8..5059b2c1c 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -220,15 +220,16 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, return image; } -- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag +- (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest callback:(RCTImageLoaderCompletionBlock)callback { - return [self loadImageWithTag:imageTag - size:CGSizeZero - scale:1 - resizeMode:RCTResizeModeStretch - progressBlock:nil - completionBlock:callback]; + return [self loadImageWithURLRequest:imageURLRequest + size:CGSizeZero + scale:1 + clipped:YES + resizeMode:RCTResizeModeStretch + progressBlock:nil + completionBlock:callback]; } - (void)dequeueTasks @@ -280,12 +281,12 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, * path taken. This is useful if you want to skip decoding, e.g. when preloading * the image, or retrieving metadata. */ -- (RCTImageLoaderCancellationBlock)loadImageOrDataWithTag:(NSString *)imageTag - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - progressBlock:(RCTImageLoaderProgressBlock)progressHandler - completionBlock:(void (^)(NSError *error, id imageOrData))completionBlock +- (RCTImageLoaderCancellationBlock)loadImageOrDataWithURLRequest:(NSURLRequest *)imageURLRequest + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(RCTResizeMode)resizeMode + progressBlock:(RCTImageLoaderProgressBlock)progressHandler + completionBlock:(void (^)(NSError *error, id imageOrData))completionBlock { __block volatile uint32_t cancelled = 0; __block void(^cancelLoad)(void) = nil; @@ -306,11 +307,6 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, } }; - if (imageTag.length == 0) { - completionHandler(RCTErrorWithMessage(@"source.uri should not be an empty string"), nil); - return ^{}; - } - // All access to URL cache must be serialized if (!_URLCacheQueue) { [self setUp]; @@ -329,7 +325,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, } // Find suitable image URL loader - NSURLRequest *request = [RCTConvert NSURLRequest:imageTag]; + NSURLRequest *request = imageURLRequest; // Use a local variable so we can reassign it in this block id loadHandler = [strongSelf imageURLLoaderForURL:request.URL]; if (loadHandler) { cancelLoad = [loadHandler loadImageForURL:request.URL @@ -345,13 +341,13 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) { RCTLogError(@"No suitable image URL loader found for %@. You may need to " " import the RCTNetwork library in order to load images.", - imageTag); + request.URL.absoluteString); return; } // Check if networking module can load image if (RCT_DEBUG && ![_bridge.networking canHandleRequest:request]) { - RCTLogError(@"No suitable image URL loader found for %@", imageTag); + RCTLogError(@"No suitable image URL loader found for %@", request.URL.absoluteString); return; } @@ -405,7 +401,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, } NSURL *redirectURL = [NSURL URLWithString: location]; - request = [NSURLRequest requestWithURL: redirectURL]; + request = [NSURLRequest requestWithURL:redirectURL]; cachedResponse = [_URLCache cachedResponseForRequest:request]; continue; } @@ -470,36 +466,20 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, }; } -- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - progressBlock:(RCTImageLoaderProgressBlock)progressHandler - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock -{ - return [self loadImageWithoutClipping:imageTag - size:size - scale:scale - resizeMode:resizeMode - progressBlock:progressHandler - completionBlock:^(NSError *error, UIImage *image) { - completionBlock(error, RCTResizeImageIfNeeded(image, size, scale, resizeMode)); - }]; -} - -- (RCTImageLoaderCancellationBlock)loadImageWithoutClipping:(NSString *)imageTag - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - progressBlock:(RCTImageLoaderProgressBlock)progressHandler - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock +- (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest + size:(CGSize)size + scale:(CGFloat)scale + clipped:(BOOL)clipped + resizeMode:(RCTResizeMode)resizeMode + progressBlock:(RCTImageLoaderProgressBlock)progressHandler + completionBlock:(RCTImageLoaderCompletionBlock)completionBlock { __block volatile uint32_t cancelled = 0; __block void(^cancelLoad)(void) = nil; __weak RCTImageLoader *weakSelf = self; // Check decoded image cache - NSString *cacheKey = RCTCacheKeyForImage(imageTag, size, scale, resizeMode); + NSString *cacheKey = RCTCacheKeyForImage(imageURLRequest.URL.absoluteString, size, scale, resizeMode); { UIImage *image = [_decodedImageCache objectForKey:cacheKey]; if (image) { @@ -527,16 +507,17 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) { cacheResultHandler(error, imageOrData); } else { - cancelLoad = [weakSelf decodeImageDataWithoutClipping:imageOrData - size:size - scale:scale - resizeMode:resizeMode - completionBlock:cacheResultHandler]; + cancelLoad = [weakSelf decodeImageData:imageOrData + size:size + scale:scale + clipped:clipped + resizeMode:resizeMode + completionBlock:cacheResultHandler]; } } }; - cancelLoad = [self loadImageOrDataWithTag:imageTag + cancelLoad = [self loadImageOrDataWithURLRequest:imageURLRequest size:size scale:scale resizeMode:resizeMode @@ -553,23 +534,9 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data size:(CGSize)size scale:(CGFloat)scale + clipped:(BOOL)clipped resizeMode:(RCTResizeMode)resizeMode completionBlock:(RCTImageLoaderCompletionBlock)completionBlock -{ - return [self decodeImageDataWithoutClipping:data - size:size - scale:scale - resizeMode:resizeMode - completionBlock:^(NSError *error, UIImage *image) { - completionBlock(error, RCTResizeImageIfNeeded(image, size, scale, resizeMode)); - }]; -} - -- (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)data - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock { if (data.length == 0) { completionBlock(RCTErrorWithMessage(@"No image data"), nil); @@ -583,11 +550,11 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, // expecting it, and may do expensive post-processing in the callback dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if (!cancelled) { - completionBlock(error, image); + completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image); } }); } else if (!cancelled) { - completionBlock(error, image); + completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image); } }; @@ -675,10 +642,10 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, } } -- (RCTImageLoaderCancellationBlock)getImageSize:(NSString *)imageTag +- (RCTImageLoaderCancellationBlock)getImageSizeForURLRequest:(NSURLRequest *)imageURLRequest block:(void(^)(NSError *error, CGSize size))completionBlock { - return [self loadImageOrDataWithTag:imageTag + return [self loadImageOrDataWithURLRequest:imageURLRequest size:CGSizeZero scale:1 resizeMode:RCTResizeModeStretch @@ -713,7 +680,7 @@ RCT_EXPORT_METHOD(prefetchImage:(NSString *)uri return; } - [_bridge.imageLoader loadImageWithTag:uri callback:^(NSError *error, UIImage *image) { + [_bridge.imageLoader loadImageWithURLRequest:[RCTConvert NSURLRequest:uri] callback:^(NSError *error, UIImage *image) { if (error) { reject(RCTErrorPrefetchFailure, nil, error); return; @@ -743,7 +710,7 @@ RCT_EXPORT_METHOD(prefetchImage:(NSString *)uri - (id)sendRequest:(NSURLRequest *)request withDelegate:(id)delegate { __block RCTImageLoaderCancellationBlock requestToken; - requestToken = [self loadImageWithTag:request.URL.absoluteString callback:^(NSError *error, UIImage *image) { + requestToken = [self loadImageWithURLRequest:request callback:^(NSError *error, UIImage *image) { if (error) { [delegate URLRequest:requestToken didCompleteWithError:error]; return; @@ -781,6 +748,90 @@ RCT_EXPORT_METHOD(prefetchImage:(NSString *)uri @end +@implementation RCTImageLoader (Deprecated) + +- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag + callback:(RCTImageLoaderCompletionBlock)callback +{ + RCTLogWarn(@"[RCTImageLoader loadImageWithTag:callback:] is deprecated. Instead use [RCTImageLoader loadImageWithURLRequest:callback:]"); + return [self loadImageWithURLRequest:[RCTConvert NSURLRequest:imageTag] + callback:callback]; +} + +- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(RCTResizeMode)resizeMode + progressBlock:(RCTImageLoaderProgressBlock)progressBlock + completionBlock:(RCTImageLoaderCompletionBlock)completionBlock +{ + RCTLogWarn(@"[RCTImageLoader loadImageWithTag:size:scale:resizeMode:progressBlock:completionBlock:] is deprecated. Instead use [RCTImageLoader loadImageWithURLRequest:size:scale:clipped:resizeMode:progressBlock:completionBlock:]"); + return [self loadImageWithURLRequest:[RCTConvert NSURLRequest:imageTag] + size:size + scale:scale + clipped:YES + resizeMode:resizeMode + progressBlock:progressBlock + completionBlock:completionBlock]; +} + +- (RCTImageLoaderCancellationBlock)loadImageWithoutClipping:(NSString *)imageTag + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(RCTResizeMode)resizeMode + progressBlock:(RCTImageLoaderProgressBlock)progressBlock + completionBlock:(RCTImageLoaderCompletionBlock)completionBlock +{ + RCTLogWarn(@"[RCTImageLoader loadImageWithoutClipping:size:scale:resizeMode:progressBlock:completionBlock:] is deprecated. Instead use [RCTImageLoader loadImageWithURLRequest:size:scale:clipped:resizeMode:progressBlock:completionBlock:]"); + return [self loadImageWithURLRequest:[RCTConvert NSURLRequest:imageTag] + size:size + scale:scale + clipped:NO + resizeMode:resizeMode + progressBlock:progressBlock + completionBlock:completionBlock]; +} + +- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(RCTResizeMode)resizeMode + completionBlock:(RCTImageLoaderCompletionBlock)completionBlock +{ + RCTLogWarn(@"[RCTImageLoader decodeImageData:size:scale:resizeMode:completionBlock:] is deprecated. Instead use [RCTImageLoader decodeImageData:size:scale:clipped:resizeMode:completionBlock:]"); + return [self decodeImageData:imageData + size:size + scale:scale + clipped:NO + resizeMode:resizeMode + completionBlock:completionBlock]; +} + +- (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)imageData + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(RCTResizeMode)resizeMode + completionBlock:(RCTImageLoaderCompletionBlock)completionBlock +{ + RCTLogWarn(@"[RCTImageLoader decodeImageDataWithoutClipping:size:scale:resizeMode:completionBlock:] is deprecated. Instead use [RCTImageLoader decodeImageData:size:scale:clipped:resizeMode:completionBlock:]"); + return [self decodeImageData:imageData + size:size + scale:scale + clipped:NO + resizeMode:resizeMode + completionBlock:completionBlock]; +} + +- (RCTImageLoaderCancellationBlock)getImageSize:(NSString *)imageTag + block:(void(^)(NSError *error, CGSize size))completionBlock +{ + RCTLogWarn(@"[RCTImageLoader getImageSize:block:] is deprecated. Instead use [RCTImageLoader getImageSizeForURLRequest:block:]"); + return [self getImageSizeForURLRequest:[RCTConvert NSURLRequest:imageTag] + block:completionBlock]; +} + +@end + @implementation RCTBridge (RCTImageLoader) - (RCTImageLoader *)imageLoader diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m index 1376fb796..a0d63d35f 100644 --- a/Libraries/Image/RCTImageView.m +++ b/Libraries/Image/RCTImageView.m @@ -224,12 +224,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) RCTImageSource *source = _source; CGFloat blurRadius = _blurRadius; __weak RCTImageView *weakSelf = self; - _reloadImageCancellationBlock = [_bridge.imageLoader loadImageWithoutClipping:_source.imageURL.absoluteString - size:imageSize - scale:imageScale - resizeMode:(RCTResizeMode)self.contentMode - progressBlock:progressHandler - completionBlock:^(NSError *error, UIImage *loadedImage) { + _reloadImageCancellationBlock = + [_bridge.imageLoader loadImageWithURLRequest:_source.request + size:imageSize + scale:imageScale + clipped:NO + resizeMode:(RCTResizeMode)self.contentMode + progressBlock:progressHandler + completionBlock:^(NSError *error, UIImage *loadedImage) { + RCTImageView *strongSelf = weakSelf; void (^setImageBlock)(UIImage *) = ^(UIImage *image) { if (![source isEqual:strongSelf.source]) { @@ -297,7 +300,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) if (RCTShouldReloadImageForSizeChange(imageSize, idealSize)) { if (RCTShouldReloadImageForSizeChange(_targetSize, idealSize)) { - RCTLogInfo(@"[PERF IMAGEVIEW] Reloading image %@ as size %@", _source.imageURL, NSStringFromCGSize(idealSize)); + RCTLogInfo(@"[PERF IMAGEVIEW] Reloading image %@ as size %@", _source.request.URL.absoluteString, NSStringFromCGSize(idealSize)); // If the existing image or an image being loaded are not the right // size, reload the asset in case there is a better size available. diff --git a/Libraries/Image/RCTImageViewManager.m b/Libraries/Image/RCTImageViewManager.m index 2bf7a0fe3..5da98b5cd 100644 --- a/Libraries/Image/RCTImageViewManager.m +++ b/Libraries/Image/RCTImageViewManager.m @@ -48,7 +48,7 @@ RCT_EXPORT_METHOD(getSize:(NSURL *)imageURL successBlock:(RCTResponseSenderBlock)successBlock errorBlock:(RCTResponseErrorBlock)errorBlock) { - [self.bridge.imageLoader getImageSize:imageURL.absoluteString + [self.bridge.imageLoader getImageSizeForURLRequest:[NSURLRequest requestWithURL:imageURL] block:^(NSError *error, CGSize size) { if (error) { errorBlock(error); diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index f5c6924d6..8c6816e9b 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -133,6 +133,23 @@ RCT_CUSTOM_CONVERTER(NSData *, NSData, [json dataUsingEncoding:NSUTF8StringEncod if ([method isEqualToString:@"GET"] && headers == nil && body == nil) { return [NSURLRequest requestWithURL:URL]; } + + if (headers) { + __block BOOL allHeadersAreStrings = YES; + [headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, id header, BOOL *stop) { + if (![header isKindOfClass:[NSString class]]) { + RCTLogError(@"Values of HTTP headers passed must be of type string. " + "Value of header '%@' is not a string.", key); + allHeadersAreStrings = NO; + *stop = YES; + } + }]; + if (!allHeadersAreStrings) { + // Set headers to nil here to avoid crashing later. + headers = nil; + } + } + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; request.HTTPBody = body; request.HTTPMethod = method; @@ -878,7 +895,7 @@ RCT_ENUM_CONVERTER(RCTAnimationType, (@{ return image; } - NSURL *URL = imageSource.imageURL; + NSURL *URL = imageSource.request.URL; NSString *scheme = URL.scheme.lowercaseString; if ([scheme isEqualToString:@"file"]) { NSString *assetName = RCTBundlePathForURL(URL); @@ -914,7 +931,7 @@ RCT_ENUM_CONVERTER(RCTAnimationType, (@{ if (!CGSizeEqualToSize(imageSource.size, CGSizeZero) && !CGSizeEqualToSize(imageSource.size, image.size)) { RCTLogError(@"Image source %@ size %@ does not match loaded image size %@.", - imageSource.imageURL.path.lastPathComponent, + URL.path.lastPathComponent, NSStringFromCGSize(imageSource.size), NSStringFromCGSize(image.size)); } diff --git a/React/Base/RCTImageSource.h b/React/Base/RCTImageSource.h index efcefaa2b..e5b52c502 100644 --- a/React/Base/RCTImageSource.h +++ b/React/Base/RCTImageSource.h @@ -16,7 +16,7 @@ */ @interface RCTImageSource : NSObject -@property (nonatomic, strong, readonly) NSURL *imageURL; +@property (nonatomic, copy, readonly) NSURLRequest *request; @property (nonatomic, assign, readonly) CGSize size; @property (nonatomic, assign, readonly) CGFloat scale; @@ -25,9 +25,9 @@ * Pass a size of CGSizeZero if you do not know or wish to specify the image * size. Pass a scale of zero if you do not know or wish to specify the scale. */ -- (instancetype)initWithURL:(NSURL *)url - size:(CGSize)size - scale:(CGFloat)scale; +- (instancetype)initWithURLRequest:(NSURLRequest *)request + size:(CGSize)size + scale:(CGFloat)scale; /** * Create a copy of the image source with the specified size and scale. @@ -36,6 +36,13 @@ @end +@interface RCTImageSource (Deprecated) + +@property (nonatomic, strong, readonly) NSURL *imageURL +__deprecated_msg("Use request.URL instead."); + +@end + @interface RCTConvert (ImageSource) + (RCTImageSource *)RCTImageSource:(id)json; diff --git a/React/Base/RCTImageSource.m b/React/Base/RCTImageSource.m index 07518edf9..173bad517 100644 --- a/React/Base/RCTImageSource.m +++ b/React/Base/RCTImageSource.m @@ -18,10 +18,10 @@ @implementation RCTImageSource -- (instancetype)initWithURL:(NSURL *)url size:(CGSize)size scale:(CGFloat)scale +- (instancetype)initWithURLRequest:(NSURLRequest *)request size:(CGSize)size scale:(CGFloat)scale { if ((self = [super init])) { - _imageURL = url; + _request = [request copy]; _size = size; _scale = scale; } @@ -30,9 +30,9 @@ - (instancetype)imageSourceWithSize:(CGSize)size scale:(CGFloat)scale { - RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURL:_imageURL - size:size - scale:scale]; + RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURLRequest:_request + size:size + scale:scale]; imageSource.packagerAsset = _packagerAsset; return imageSource; } @@ -42,12 +42,22 @@ if (![object isKindOfClass:[RCTImageSource class]]) { return NO; } - return [_imageURL isEqual:object.imageURL] && _scale == object.scale && + return [_request isEqual:object.request] && _scale == object.scale && (CGSizeEqualToSize(_size, object.size) || CGSizeEqualToSize(object.size, CGSizeZero)); } @end + +@implementation RCTImageSource (Deprecated) + +- (NSURL *)imageURL +{ + return self.request.URL; +} + +@end + @implementation RCTConvert (ImageSource) + (RCTImageSource *)RCTImageSource:(id)json @@ -56,25 +66,25 @@ return nil; } - NSURL *imageURL; + NSURLRequest *request; CGSize size = CGSizeZero; CGFloat scale = 1.0; BOOL packagerAsset = NO; if ([json isKindOfClass:[NSDictionary class]]) { - if (!(imageURL = [self NSURL:RCTNilIfNull(json[@"uri"])])) { + if (!(request = [self NSURLRequest:json])) { return nil; } size = [self CGSize:json]; scale = [self CGFloat:json[@"scale"]] ?: [self BOOL:json[@"deprecated"]] ? 0.0 : 1.0; packagerAsset = [self BOOL:json[@"__packager_asset"]]; } else if ([json isKindOfClass:[NSString class]]) { - imageURL = [self NSURL:json]; + request = [self NSURLRequest:json]; } else { RCTLogConvertError(json, @"an image. Did you forget to call resolveAssetSource() on the JS side?"); return nil; } - RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURL:imageURL + RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURLRequest:request size:size scale:scale]; imageSource.packagerAsset = packagerAsset;