Skip to content

Commit

Permalink
iOS: Support HTTP headers for source prop on <Image> components
Browse files Browse the repository at this point in the history
Summary:
Allows developers to specify headers to include in the HTTP request
when fetching a remote image. For example, one might leverage this
when fetching an image from an endpoint that requires authentication:

```
<Image
  style={styles.logo}
  source={{
    uri: 'http://facebook.github.io/react/img/logo_og.png',
    headers: {
      Authorization: 'someAuthToken'
    }
  }}
/>
```

Note that the header values must be strings.

Works on iOS and Android.

**Test plan (required)**

- Ran a small example like the one above on iOS and Android and ensured the headers were sent to the server.
- Ran a small example to ensure that \<Image\> components without headers still work.
- Currently using this code in our app.

Adam Comella
Microsoft Corp.
Closes #7338

Reviewed By: javache

Differential Revision: D3371458

Pulled By: nicklockwood

fbshipit-source-id: cdb24fe2572c3ae3ba82c86ad383af6d85157e20
  • Loading branch information
Adam Comella authored and Facebook Github Bot 1 committed Jun 1, 2016
1 parent cec913e commit ee8496f
Show file tree
Hide file tree
Showing 12 changed files with 331 additions and 142 deletions.
10 changes: 6 additions & 4 deletions Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ - (void)testImageLoading

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) {
Expand Down Expand Up @@ -79,7 +80,8 @@ - (void)testImageLoaderUsesImageURLLoaderWithHighestPriority

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) {
Expand All @@ -103,7 +105,7 @@ - (void)testImageDecoding

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);
}];
Expand Down Expand Up @@ -132,7 +134,7 @@ - (void)testImageLoaderUsesImageDecoderWithHighestPriority

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);
}];
Expand Down
5 changes: 3 additions & 2 deletions Libraries/CameraRoll/RCTCameraRollManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,12 @@ @implementation RCTCameraRollManager
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;
Expand Down
37 changes: 25 additions & 12 deletions Libraries/Image/Image.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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 `<Image/>` 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,
Expand Down Expand Up @@ -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]) || {};

Expand All @@ -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 <Image> component requires a `source` property rather than `src`.');
}
Expand Down
56 changes: 56 additions & 0 deletions Libraries/Image/ImageSourcePropType.js
Original file line number Diff line number Diff line change
@@ -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 `<Image/>` 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;
6 changes: 3 additions & 3 deletions Libraries/Image/RCTImageEditingManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ @implementation RCTImageEditingManager
/**
* 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)
Expand All @@ -45,7 +45,7 @@ @implementation RCTImageEditingManager
[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;
Expand Down
77 changes: 53 additions & 24 deletions Libraries/Image/RCTImageLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -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. 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)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
size:(CGSize)size
scale:(CGFloat)scale
clipped:(BOOL)clipped
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. 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;

/**
* As above, but includes target size, scale and resizeMode, which are used to
* select the optimal dimensions for the loaded image.
* 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;
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
__deprecated_msg("Use loadImageWithURLRequest:size:scale:clipped:resizeMode:progressBlock:completionBlock: instead");

/**
* 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;
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
__deprecated_msg("Use loadImageWithURLRequest:size:scale:clipped:resizeMode:progressBlock:completionBlock: instead");

/**
* 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.
*/
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
__deprecated_msg("Use decodeImageData:size:scale:clipped:resizeMode:completionBlock: instead");

/**
* 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;
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
__deprecated_msg("Use decodeImageData:size:scale:clipped:resizeMode:completionBlock: instead");

/**
* 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)getImageSize:(NSString *)imageTag
block:(void(^)(NSError *error, CGSize size))completionBlock;
block:(void(^)(NSError *error, CGSize size))completionBlock
__deprecated_msg("Use getImageSizeWithURLRequest:callback: instead");

@end

Expand Down
Loading

0 comments on commit ee8496f

Please sign in to comment.