Merged RCTNetworkImageView functionality into RCTStaticImage
Summary: RCTNetworkImageView and RCTStaticImage had significant overlap in functionality, but each had a different subset of features and bugs. This diff merges most of the functionality of RCTNetworkImageView into RCTStaticImage, eliminating some bugs in the former, such as constant redrawing when properties were changed. I've also removed the onLoadAbort event for now (as it wasn't implemented), and renamed the other events to match the web specs for `<img>` and XHMLHttpRequest. The API is essentially what Adobe proposed here: http://blogs.adobe.com/webplatform/2012/01/13/html5-image-progress-events/ The following features have not yet been ported from RCTNetworkImageView: - Background color compositing. It's not clear that this adds much value and it increases memory consumption, etc. - Image request cancelling when images are removed from view. Again, it's not clear if this is a huge benefit, but if it is it should be combined with other optimisations, such as unloading offscreen images. (Note that this only affects the open source fork. For now, internal apps will still use FBNetworkImageView for remote images.)
This commit is contained in:
parent
82a774e92b
commit
61c648d564
|
@ -32,7 +32,7 @@ var NetworkImageExample = React.createClass({
|
|||
getInitialState: function() {
|
||||
return {
|
||||
error: false,
|
||||
loading: true,
|
||||
loading: false,
|
||||
progress: 0
|
||||
};
|
||||
},
|
||||
|
@ -47,10 +47,10 @@ var NetworkImageExample = React.createClass({
|
|||
<Image
|
||||
source={this.props.source}
|
||||
style={[styles.base, {overflow: 'visible'}]}
|
||||
onLoadError={(e) => this.setState({error: e.nativeEvent.error})}
|
||||
onLoadProgress={(e) => this.setState({progress: Math.max(0, Math.round(100 * e.nativeEvent.written / e.nativeEvent.total))}) }
|
||||
onLoadEnd={() => this.setState({loading: false, error: false})}
|
||||
onLoadAbort={() => this.setState({error: 'Loading has aborted'})} >
|
||||
onLoadStart={(e) => this.setState({loading: true})}
|
||||
onError={(e) => this.setState({error: e.nativeEvent.error, loading: false})}
|
||||
onProgress={(e) => this.setState({progress: Math.round(100 * e.nativeEvent.loaded / e.nativeEvent.total)})}
|
||||
onLoad={() => this.setState({loading: false, error: false})}>
|
||||
{loader}
|
||||
</Image>;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ var StyleSheetPropType = require('StyleSheetPropType');
|
|||
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var invariant = require('invariant');
|
||||
var merge = require('merge');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var resolveAssetSource = require('resolveAssetSource');
|
||||
var verifyPropTypes = require('verifyPropTypes');
|
||||
|
@ -57,6 +56,7 @@ var warning = require('warning');
|
|||
|
||||
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
|
||||
|
@ -93,7 +93,6 @@ var Image = React.createClass({
|
|||
* image dimensions.
|
||||
*/
|
||||
resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']),
|
||||
style: StyleSheetPropType(ImageStylePropTypes),
|
||||
/**
|
||||
* A unique identifier for this element to be used in UI Automation
|
||||
* testing scripts.
|
||||
|
@ -102,7 +101,7 @@ var Image = React.createClass({
|
|||
/**
|
||||
* Invoked on mount and layout changes with
|
||||
*
|
||||
* {nativeEvent: { layout: {x, y, width, height}}}.
|
||||
* {nativeEvent: {layout: {x, y, width, height}}}.
|
||||
*/
|
||||
onLayout: PropTypes.func,
|
||||
/**
|
||||
|
@ -112,25 +111,23 @@ var Image = React.createClass({
|
|||
/**
|
||||
* Invoked on download progress with
|
||||
*
|
||||
* {nativeEvent: { written, total}}.
|
||||
* {nativeEvent: {loaded, total}}.
|
||||
*/
|
||||
onLoadProgress: PropTypes.func,
|
||||
/**
|
||||
* Invoked on load abort
|
||||
*/
|
||||
onLoadAbort: PropTypes.func,
|
||||
onProgress: PropTypes.func,
|
||||
/**
|
||||
* Invoked on load error
|
||||
*
|
||||
* {nativeEvent: { error}}.
|
||||
* {nativeEvent: {error}}.
|
||||
*/
|
||||
onLoadError: PropTypes.func,
|
||||
onError: PropTypes.func,
|
||||
/**
|
||||
* Invoked on load end
|
||||
*
|
||||
* Invoked when load completes successfully
|
||||
*/
|
||||
onLoaded: PropTypes.func
|
||||
|
||||
onLoad: PropTypes.func,
|
||||
/**
|
||||
* Invoked when load either succeeds or fails
|
||||
*/
|
||||
onLoadEnd: PropTypes.func,
|
||||
},
|
||||
|
||||
statics: {
|
||||
|
@ -149,46 +146,27 @@ var Image = React.createClass({
|
|||
},
|
||||
|
||||
render: function() {
|
||||
for (var prop in nativeOnlyProps) {
|
||||
if (this.props[prop] !== undefined) {
|
||||
console.warn('Prop `' + prop + ' = ' + this.props[prop] + '` should ' +
|
||||
'not be set directly on Image.');
|
||||
}
|
||||
}
|
||||
var source = resolveAssetSource(this.props.source) || {};
|
||||
var defaultSource = (this.props.defaultSource && resolveAssetSource(this.props.defaultSource)) || {};
|
||||
|
||||
var {width, height} = source;
|
||||
var style = flattenStyle([{width, height}, styles.base, this.props.style]);
|
||||
invariant(style, 'style must be initialized');
|
||||
var style = flattenStyle([{width, height}, styles.base, this.props.style]) || {};
|
||||
|
||||
var isNetwork = source.uri && source.uri.match(/^https?:/);
|
||||
invariant(
|
||||
!(isNetwork && source.isStatic),
|
||||
'static image uris cannot start with "http": "' + source.uri + '"'
|
||||
var RawImage = isNetwork ? RCTNetworkImageView : RCTImageView;
|
||||
var resizeMode = this.props.resizeMode || (style || {}).resizeMode || 'cover'; // Workaround for flow bug t7737108
|
||||
var tintColor = (style || {}).tintColor; // Workaround for flow bug t7737108
|
||||
|
||||
return (
|
||||
<RawImage
|
||||
{...this.props}
|
||||
style={style}
|
||||
resizeMode={resizeMode}
|
||||
tintColor={tintColor}
|
||||
src={source.uri}
|
||||
defaultSrc={defaultSource.uri}
|
||||
/>
|
||||
);
|
||||
var isStored = !source.isStatic && !isNetwork;
|
||||
var RawImage = isNetwork ? RCTNetworkImage : RCTStaticImage;
|
||||
|
||||
if (this.props.style && this.props.style.tintColor) {
|
||||
warning(RawImage === RCTStaticImage, 'tintColor style only supported on static images.');
|
||||
}
|
||||
var resizeMode = this.props.resizeMode || style.resizeMode || 'cover';
|
||||
|
||||
var nativeProps = merge(this.props, {
|
||||
style,
|
||||
resizeMode,
|
||||
tintColor: style.tintColor,
|
||||
});
|
||||
if (isStored) {
|
||||
nativeProps.imageTag = source.uri;
|
||||
} else {
|
||||
nativeProps.src = source.uri;
|
||||
}
|
||||
if (this.props.defaultSource) {
|
||||
nativeProps.defaultImageSrc = this.props.defaultSource.uri;
|
||||
}
|
||||
nativeProps.progressHandlerRegistered = isNetwork && this.props.onLoadProgress;
|
||||
return <RawImage {...nativeProps} />;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -198,18 +176,7 @@ var styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
var RCTNetworkImage = requireNativeComponent('RCTNetworkImageView', null);
|
||||
var RCTStaticImage = requireNativeComponent('RCTStaticImage', null);
|
||||
|
||||
var nativeOnlyProps = {
|
||||
src: true,
|
||||
defaultImageSrc: true,
|
||||
imageTag: true,
|
||||
progressHandlerRegistered: true
|
||||
};
|
||||
if (__DEV__) {
|
||||
verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps);
|
||||
verifyPropTypes(Image, RCTNetworkImage.viewConfig, nativeOnlyProps);
|
||||
}
|
||||
var RCTImageView = requireNativeComponent('RCTImageView', null);
|
||||
var RCTNetworkImageView = (NativeModules.NetworkImageViewManager) ? requireNativeComponent('RCTNetworkImageView', null) : RCTImageView;
|
||||
|
||||
module.exports = Image;
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */; };
|
||||
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */; };
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; };
|
||||
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 */; };
|
||||
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */; };
|
||||
|
@ -17,8 +17,6 @@
|
|||
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
|
||||
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
|
||||
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
|
||||
58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */; };
|
||||
58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
|
@ -36,10 +34,10 @@
|
|||
/* Begin PBXFileReference section */
|
||||
03559E7D1B064D3A00730281 /* RCTDownloadTaskWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTDownloadTaskWrapper.h; sourceTree = "<group>"; };
|
||||
03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTaskWrapper.m; sourceTree = "<group>"; };
|
||||
1304D5A71AA8C4A30002E2BE /* RCTStaticImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImage.h; sourceTree = "<group>"; };
|
||||
1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImage.m; sourceTree = "<group>"; };
|
||||
1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImageManager.h; sourceTree = "<group>"; };
|
||||
1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImageManager.m; sourceTree = "<group>"; };
|
||||
1304D5A71AA8C4A30002E2BE /* RCTImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageView.h; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
|
@ -55,10 +53,6 @@
|
|||
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>"; };
|
||||
58B5118B1A9E6BD600147676 /* RCTNetworkImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworkImageView.h; sourceTree = "<group>"; };
|
||||
58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkImageView.m; sourceTree = "<group>"; };
|
||||
58B5118D1A9E6BD600147676 /* RCTNetworkImageViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworkImageViewManager.h; sourceTree = "<group>"; };
|
||||
58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkImageViewManager.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -89,14 +83,10 @@
|
|||
137620341B31C53500677FF0 /* RCTImagePickerManager.m */,
|
||||
1345A8371B26592900583190 /* RCTImageRequestHandler.h */,
|
||||
1345A8381B26592900583190 /* RCTImageRequestHandler.m */,
|
||||
58B5118B1A9E6BD600147676 /* RCTNetworkImageView.h */,
|
||||
58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */,
|
||||
58B5118D1A9E6BD600147676 /* RCTNetworkImageViewManager.h */,
|
||||
58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */,
|
||||
1304D5A71AA8C4A30002E2BE /* RCTStaticImage.h */,
|
||||
1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */,
|
||||
1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */,
|
||||
1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */,
|
||||
1304D5A71AA8C4A30002E2BE /* RCTImageView.h */,
|
||||
1304D5A81AA8C4A30002E2BE /* RCTImageView.m */,
|
||||
1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */,
|
||||
1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */,
|
||||
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */,
|
||||
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */,
|
||||
58B5115E1A9E6B3D00147676 /* Products */,
|
||||
|
@ -171,15 +161,13 @@
|
|||
files = (
|
||||
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
|
||||
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
|
||||
58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */,
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */,
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */,
|
||||
1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */,
|
||||
58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */,
|
||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */,
|
||||
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
|
||||
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
|
||||
03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */,
|
||||
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */,
|
||||
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,
|
||||
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
@ -43,11 +43,4 @@ typedef void (^RCTImageDownloadCancellationBlock)(void);
|
|||
progressBlock:(RCTDataProgressBlock)progressBlock
|
||||
block:(RCTImageDownloadBlock)block;
|
||||
|
||||
/**
|
||||
* Cancel an in-flight download. If multiple requets have been made for the
|
||||
* same image, only the request that relates to the token passed will be
|
||||
* cancelled.
|
||||
*/
|
||||
- (void)cancelDownload:(RCTImageDownloadCancellationBlock)downloadToken;
|
||||
|
||||
@end
|
||||
|
|
|
@ -52,7 +52,9 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
|
|||
return self;
|
||||
}
|
||||
|
||||
- (RCTImageDownloadCancellationBlock)_downloadDataForURL:(NSURL *)url progressBlock:progressBlock block:(RCTCachedDataDownloadBlock)block
|
||||
- (RCTImageDownloadCancellationBlock)_downloadDataForURL:(NSURL *)url
|
||||
progressBlock:progressBlock
|
||||
block:(RCTCachedDataDownloadBlock)block
|
||||
{
|
||||
NSString *const cacheKey = url.absoluteString;
|
||||
|
||||
|
@ -134,7 +136,9 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
|
|||
return [cancel copy];
|
||||
}
|
||||
|
||||
- (RCTImageDownloadCancellationBlock)downloadDataForURL:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock block:(RCTDataDownloadBlock)block
|
||||
- (RCTImageDownloadCancellationBlock)downloadDataForURL:(NSURL *)url
|
||||
progressBlock:(RCTDataProgressBlock)progressBlock
|
||||
block:(RCTDataDownloadBlock)block
|
||||
{
|
||||
return [self _downloadDataForURL:url progressBlock:progressBlock block:^(BOOL cached, NSURLResponse *response, NSData *data, NSError *error) {
|
||||
block(data, error);
|
||||
|
@ -150,24 +154,19 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
|
|||
progressBlock:(RCTDataProgressBlock)progressBlock
|
||||
block:(RCTImageDownloadBlock)block
|
||||
{
|
||||
scale = scale ?: RCTScreenScale();
|
||||
|
||||
return [self downloadDataForURL:url progressBlock:progressBlock block:^(NSData *data, NSError *error) {
|
||||
if (!data || error) {
|
||||
block(nil, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (CGSizeEqualToSize(size, CGSizeZero)) {
|
||||
// Target size wasn't available yet, so abort image drawing
|
||||
block(nil, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
UIImage *image = [UIImage imageWithData:data scale:scale];
|
||||
if (image) {
|
||||
if (image && !CGSizeEqualToSize(size, CGSizeZero)) {
|
||||
|
||||
// Get scale and size
|
||||
CGFloat destScale = scale ?: RCTScreenScale();
|
||||
CGRect imageRect = RCTClipRect(image.size, image.scale, size, destScale, resizeMode);
|
||||
CGRect imageRect = RCTClipRect(image.size, scale, size, scale, resizeMode);
|
||||
CGSize destSize = RCTTargetSizeForClipRect(imageRect);
|
||||
|
||||
// Opacity optimizations
|
||||
|
@ -183,7 +182,7 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
|
|||
}
|
||||
|
||||
// Decompress image at required size
|
||||
UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale);
|
||||
UIGraphicsBeginImageContextWithOptions(destSize, opaque, scale);
|
||||
if (blendColor) {
|
||||
[blendColor setFill];
|
||||
UIRectFill((CGRect){CGPointZero, destSize});
|
||||
|
@ -201,11 +200,4 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
|
|||
}];
|
||||
}
|
||||
|
||||
- (void)cancelDownload:(RCTImageDownloadCancellationBlock)downloadToken
|
||||
{
|
||||
if (downloadToken) {
|
||||
downloadToken();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
|
||||
@class ALAssetsLibrary;
|
||||
|
||||
typedef void (^RCTImageLoaderProgressBlock)(int64_t written, int64_t total);
|
||||
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id /* UIImage or CAAnimation */);
|
||||
typedef void (^RCTImageLoaderCancellationBlock)(void);
|
||||
|
||||
@interface RCTImageLoader : NSObject
|
||||
|
||||
/**
|
||||
|
@ -22,22 +26,28 @@
|
|||
* Can be called from any thread.
|
||||
* Will always call callback on main thread.
|
||||
*/
|
||||
+ (void)loadImageWithTag:(NSString *)imageTag
|
||||
callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback;
|
||||
+ (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
||||
callback:(RCTImageLoaderCompletionBlock)callback;
|
||||
|
||||
/**
|
||||
* As above, but includes target size, scale and resizeMode, which are used to
|
||||
* select the optimal dimensions for the loaded image.
|
||||
*/
|
||||
+ (void)loadImageWithTag:(NSString *)imageTag
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback;
|
||||
+ (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressBlock:(RCTImageLoaderProgressBlock)progress
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completion;
|
||||
|
||||
/**
|
||||
* Is the specified image tag an asset library image?
|
||||
*/
|
||||
+ (BOOL)isAssetLibraryImage:(NSString *)imageTag;
|
||||
|
||||
/**
|
||||
* Is the specified image tag a remote image?
|
||||
*/
|
||||
+ (BOOL)isRemoteImage:(NSString *)imageTag;
|
||||
|
||||
@end
|
||||
|
|
|
@ -57,21 +57,23 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
|
|||
return assetsLibrary;
|
||||
}
|
||||
|
||||
+ (void)loadImageWithTag:(NSString *)imageTag
|
||||
callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback
|
||||
+ (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
||||
callback:(RCTImageLoaderCompletionBlock)callback
|
||||
{
|
||||
return [self loadImageWithTag:imageTag
|
||||
size:CGSizeZero
|
||||
scale:0
|
||||
resizeMode:UIViewContentModeScaleToFill
|
||||
callback:callback];
|
||||
progressBlock:nil
|
||||
completionBlock:callback];
|
||||
}
|
||||
|
||||
+ (void)loadImageWithTag:(NSString *)imageTag
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
callback:(void (^)(NSError *error, id image))callback
|
||||
+ (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressBlock:(RCTImageLoaderProgressBlock)progress
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completion
|
||||
{
|
||||
if ([imageTag hasPrefix:@"assets-library://"]) {
|
||||
[[RCTImageLoader assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) {
|
||||
|
@ -109,19 +111,20 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
|
|||
}
|
||||
|
||||
UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale orientation:(UIImageOrientation)orientation];
|
||||
RCTDispatchCallbackOnMainQueue(callback, nil, image);
|
||||
RCTDispatchCallbackOnMainQueue(completion, nil, image);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, nil);
|
||||
}
|
||||
} failureBlock:^(NSError *loadError) {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageTag, loadError];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, nil);
|
||||
}];
|
||||
return ^{};
|
||||
} else if ([imageTag hasPrefix:@"ph://"]) {
|
||||
// Using PhotoKit for iOS 8+
|
||||
// The 'ph://' prefix is used by FBMediaKit to differentiate between
|
||||
|
@ -132,8 +135,8 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
|
|||
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(callback, error, nil);
|
||||
return;
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, nil);
|
||||
return ^{};
|
||||
}
|
||||
|
||||
PHAsset *asset = [results firstObject];
|
||||
|
@ -144,59 +147,67 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
|
|||
}
|
||||
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:nil resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
if (result) {
|
||||
RCTDispatchCallbackOnMainQueue(callback, nil, result);
|
||||
RCTDispatchCallbackOnMainQueue(completion, nil, result);
|
||||
} else {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load PHAsset with local identifier %@ with no error message.", phAssetID];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, nil);
|
||||
return;
|
||||
}
|
||||
}];
|
||||
return ^{};
|
||||
} else if ([imageTag hasPrefix:@"http"]) {
|
||||
NSURL *url = [NSURL URLWithString:imageTag];
|
||||
if (!url) {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Invalid URL: %@", imageTag];
|
||||
RCTDispatchCallbackOnMainQueue(callback, RCTErrorWithMessage(errorMessage), nil);
|
||||
return;
|
||||
RCTDispatchCallbackOnMainQueue(completion, RCTErrorWithMessage(errorMessage), nil);
|
||||
return ^{};
|
||||
}
|
||||
if ([[imageTag lowercaseString] hasSuffix:@".gif"]) {
|
||||
[[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:nil block:^(NSData *data, NSError *error) {
|
||||
if ([imageTag.lowercaseString hasSuffix:@".gif"]) {
|
||||
return [[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:progress block:^(NSData *data, NSError *error) {
|
||||
id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
|
||||
if (!image && !error) {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", imageTag];
|
||||
error = RCTErrorWithMessage(errorMessage);
|
||||
}
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, image);
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, image);
|
||||
}];
|
||||
} else {
|
||||
[[RCTImageDownloader sharedInstance] downloadImageForURL:url size:size scale:scale resizeMode:resizeMode tintColor:nil backgroundColor:nil progressBlock:NULL block:^(UIImage *image, NSError *error) {
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, image);
|
||||
return [[RCTImageDownloader sharedInstance] downloadImageForURL:url size:size scale:scale resizeMode:resizeMode tintColor:nil backgroundColor:nil progressBlock:progress block:^(UIImage *image, NSError *error) {
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, image);
|
||||
}];
|
||||
}
|
||||
} else if ([[imageTag lowercaseString] hasSuffix:@".gif"]) {
|
||||
} else if ([imageTag.lowercaseString hasSuffix:@".gif"]) {
|
||||
id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
|
||||
if (image) {
|
||||
RCTDispatchCallbackOnMainQueue(callback, nil, image);
|
||||
RCTDispatchCallbackOnMainQueue(completion, nil, image);
|
||||
} else {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorMessage);
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, nil);
|
||||
}
|
||||
return ^{};
|
||||
} else {
|
||||
UIImage *image = [RCTConvert UIImage:imageTag];
|
||||
if (image) {
|
||||
RCTDispatchCallbackOnMainQueue(callback, nil, image);
|
||||
RCTDispatchCallbackOnMainQueue(completion, nil, image);
|
||||
} else {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unrecognized tag protocol: %@", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorMessage);
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, nil);
|
||||
}
|
||||
return ^{};
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)isAssetLibraryImage:(NSString *)imageTag
|
||||
{
|
||||
return [imageTag hasPrefix:@"assets-library://"] || [imageTag hasPrefix:@"ph:"];
|
||||
return [imageTag hasPrefix:@"assets-library://"] || [imageTag hasPrefix:@"ph://"];
|
||||
}
|
||||
|
||||
+ (BOOL)isRemoteImage:(NSString *)imageTag
|
||||
{
|
||||
return [imageTag hasPrefix:@"http://"] || [imageTag hasPrefix:@"https://"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -9,9 +9,14 @@
|
|||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface RCTStaticImage : UIImageView
|
||||
@class RCTBridge;
|
||||
|
||||
@interface RCTImageView : UIImageView
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property (nonatomic, assign) UIEdgeInsets capInsets;
|
||||
@property (nonatomic, strong) UIImage *defaultImage;
|
||||
@property (nonatomic, assign) UIImageRenderingMode renderingMode;
|
||||
@property (nonatomic, copy) NSString *src;
|
||||
|
|
@ -7,16 +7,41 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTStaticImage.h"
|
||||
#import "RCTImageView.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTGIFImage.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
#import "UIView+React.h"
|
||||
|
||||
@implementation RCTStaticImage
|
||||
@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;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTImageView
|
||||
{
|
||||
RCTBridge *_bridge;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_bridge = bridge;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(-init)
|
||||
|
||||
- (void)_updateImage
|
||||
{
|
||||
|
@ -45,7 +70,7 @@
|
|||
- (void)setImage:(UIImage *)image
|
||||
{
|
||||
if (image != super.image) {
|
||||
super.image = image;
|
||||
super.image = image ?: _defaultImage;
|
||||
[self _updateImage];
|
||||
}
|
||||
}
|
||||
|
@ -77,19 +102,55 @@
|
|||
- (void)reloadImage
|
||||
{
|
||||
if (_src && !CGSizeEqualToSize(self.frame.size, CGSizeZero)) {
|
||||
|
||||
if (_onLoadStart) {
|
||||
NSDictionary *event = @{ @"target": self.reactTag };
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"loadStart" body:event];
|
||||
}
|
||||
|
||||
RCTImageLoaderProgressBlock progressHandler = nil;
|
||||
if (_onProgress) {
|
||||
progressHandler = ^(int64_t loaded, int64_t total) {
|
||||
NSDictionary *event = @{
|
||||
@"target": self.reactTag,
|
||||
@"loaded": @(loaded),
|
||||
@"total": @(total),
|
||||
};
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"progress" body:event];
|
||||
};
|
||||
}
|
||||
|
||||
[RCTImageLoader loadImageWithTag:_src
|
||||
size:self.bounds.size
|
||||
scale:RCTScreenScale()
|
||||
resizeMode:self.contentMode callback:^(NSError *error, id image) {
|
||||
if (error) {
|
||||
RCTLogWarn(@"%@", error.localizedDescription);
|
||||
}
|
||||
resizeMode:self.contentMode
|
||||
progressBlock:progressHandler
|
||||
completionBlock:^(NSError *error, id image) {
|
||||
|
||||
if ([image isKindOfClass:[CAAnimation class]]) {
|
||||
[self.layer addAnimation:image forKey:@"contents"];
|
||||
} else {
|
||||
[self.layer removeAnimationForKey:@"contents"];
|
||||
self.image = image;
|
||||
}
|
||||
if (error) {
|
||||
if (_onError) {
|
||||
NSDictionary *event = @{
|
||||
@"target": self.reactTag,
|
||||
@"error": error.localizedDescription,
|
||||
};
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"error" body:event];
|
||||
}
|
||||
} else {
|
||||
if (_onLoad) {
|
||||
NSDictionary *event = @{ @"target": self.reactTag };
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"load" body:event];
|
||||
}
|
||||
}
|
||||
if (_onLoadEnd) {
|
||||
NSDictionary *event = @{ @"target": self.reactTag };
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"loadEnd" body:event];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
[self.layer removeAnimationForKey:@"contents"];
|
||||
|
@ -102,7 +163,7 @@
|
|||
[super reactSetFrame:frame];
|
||||
if (self.image == nil) {
|
||||
[self reloadImage];
|
||||
} else if ([RCTImageLoader isAssetLibraryImage:_src]) {
|
||||
} else if ([RCTImageLoader isAssetLibraryImage:_src] || [RCTImageLoader isRemoteImage:_src]) {
|
||||
CGSize imageSize = {
|
||||
self.image.size.width / RCTScreenScale(),
|
||||
self.image.size.height / RCTScreenScale()
|
|
@ -9,6 +9,6 @@
|
|||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTStaticImageManager : RCTViewManager
|
||||
@interface RCTImageViewManager : RCTViewManager
|
||||
|
||||
@end
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* 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 "RCTImageViewManager.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTImageView.h"
|
||||
|
||||
@implementation RCTImageViewManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[RCTImageView alloc] initWithBridge:self.bridge];
|
||||
}
|
||||
|
||||
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_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView)
|
||||
{
|
||||
if (json) {
|
||||
view.renderingMode = UIImageRenderingModeAlwaysTemplate;
|
||||
view.tintColor = [RCTConvert UIColor:json];
|
||||
} else {
|
||||
view.renderingMode = defaultView.renderingMode;
|
||||
view.tintColor = defaultView.tintColor;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *)customDirectEventTypes
|
||||
{
|
||||
return @{
|
||||
@"loadStart": @{ @"registrationName": @"onLoadStart" },
|
||||
@"progress": @{ @"registrationName": @"onProgress" },
|
||||
@"error": @{ @"registrationName": @"onError" },
|
||||
@"load": @{ @"registrationName": @"onLoad" },
|
||||
@"loadEnd": @{ @"registrationName": @"onLoadEnd" },
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,46 +0,0 @@
|
|||
/**
|
||||
* 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 <UIKit/UIKit.h>
|
||||
|
||||
@class RCTEventDispatcher;
|
||||
@class RCTImageDownloader;
|
||||
|
||||
@interface RCTNetworkImageView : UIView
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
|
||||
imageDownloader:(RCTImageDownloader *)imageDownloader NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* An image that will appear while the view is loading the image from the network,
|
||||
* or when imageURL is nil. Defaults to nil.
|
||||
*/
|
||||
@property (nonatomic, strong) UIImage *defaultImage;
|
||||
|
||||
/**
|
||||
* Specify a URL for an image. The image will be asynchronously loaded and displayed.
|
||||
*/
|
||||
@property (nonatomic, strong) NSURL *imageURL;
|
||||
|
||||
/**
|
||||
* Whether the image should be masked with this view's tint color.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL tinted;
|
||||
|
||||
/**
|
||||
* By default, changing imageURL will reset whatever existing image was present
|
||||
* and revert to defaultImage while the new image loads. In certain obscure cases you
|
||||
* may want to disable this behavior and instead keep displaying the previous image
|
||||
* while the new one loads. In this case, pass NO for resetToDefaultImageWhileLoading.
|
||||
* (If you set imageURL to nil, however, resetToDefaultImageWhileLoading is ignored;
|
||||
* that will always reset to the default image.)
|
||||
*/
|
||||
- (void)setImageURL:(NSURL *)imageURL resetToDefaultImageWhileLoading:(BOOL)reset;
|
||||
|
||||
@end
|
|
@ -1,220 +0,0 @@
|
|||
/**
|
||||
* 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 "RCTNetworkImageView.h"
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTGIFImage.h"
|
||||
#import "RCTImageDownloader.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
@implementation RCTNetworkImageView
|
||||
{
|
||||
BOOL _deferred;
|
||||
BOOL _progressHandlerRegistered;
|
||||
NSURL *_imageURL;
|
||||
NSURL *_deferredImageURL;
|
||||
NSUInteger _deferSentinel;
|
||||
RCTImageDownloader *_imageDownloader;
|
||||
id _downloadToken;
|
||||
RCTEventDispatcher *_eventDispatcher;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher imageDownloader:(RCTImageDownloader *)imageDownloader
|
||||
{
|
||||
if ((self = [super initWithFrame:CGRectZero])) {
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_progressHandlerRegistered = NO;
|
||||
_deferSentinel = 0;
|
||||
_imageDownloader = imageDownloader;
|
||||
self.userInteractionEnabled = NO;
|
||||
self.contentMode = UIViewContentModeScaleAspectFill;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(-initWithFrame:(CGRect)frame)
|
||||
RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
|
||||
|
||||
- (NSURL *)imageURL
|
||||
{
|
||||
// We clear our imageURL when we are not in a window for a while,
|
||||
// to make sure we don't consume network resources while offscreen.
|
||||
// However we don't want to expose this hackery externally.
|
||||
return _deferred ? _deferredImageURL : _imageURL;
|
||||
}
|
||||
|
||||
- (void)setBackgroundColor:(UIColor *)backgroundColor
|
||||
{
|
||||
super.backgroundColor = backgroundColor;
|
||||
[self _updateImage];
|
||||
}
|
||||
|
||||
- (void)setTintColor:(UIColor *)tintColor
|
||||
{
|
||||
super.tintColor = tintColor;
|
||||
[self _updateImage];
|
||||
}
|
||||
|
||||
- (void)setProgressHandlerRegistered:(BOOL)progressHandlerRegistered
|
||||
{
|
||||
_progressHandlerRegistered = progressHandlerRegistered;
|
||||
}
|
||||
|
||||
- (void)reactSetFrame:(CGRect)frame
|
||||
{
|
||||
[super reactSetFrame:frame];
|
||||
[self _updateImage];
|
||||
}
|
||||
|
||||
- (void)_updateImage
|
||||
{
|
||||
[self setImageURL:_imageURL resetToDefaultImageWhileLoading:NO];
|
||||
}
|
||||
|
||||
- (void)setImageURL:(NSURL *)imageURL resetToDefaultImageWhileLoading:(BOOL)reset
|
||||
{
|
||||
if (![_imageURL isEqual:imageURL] && _downloadToken) {
|
||||
[_imageDownloader cancelDownload:_downloadToken];
|
||||
NSDictionary *event = @{ @"target": self.reactTag };
|
||||
[_eventDispatcher sendInputEventWithName:@"loadAbort" body:event];
|
||||
_downloadToken = nil;
|
||||
}
|
||||
|
||||
_imageURL = imageURL;
|
||||
|
||||
if (_deferred) {
|
||||
_deferredImageURL = imageURL;
|
||||
} else {
|
||||
if (!imageURL) {
|
||||
self.layer.contents = nil;
|
||||
return;
|
||||
}
|
||||
if (reset) {
|
||||
self.layer.contentsScale = _defaultImage.scale;
|
||||
self.layer.contents = (__bridge id)_defaultImage.CGImage;
|
||||
self.layer.minificationFilter = kCAFilterTrilinear;
|
||||
self.layer.magnificationFilter = kCAFilterTrilinear;
|
||||
}
|
||||
[_eventDispatcher sendInputEventWithName:@"loadStart" body:@{ @"target": self.reactTag }];
|
||||
|
||||
RCTDataProgressBlock progressHandler = ^(int64_t written, int64_t total) {
|
||||
if (_progressHandlerRegistered) {
|
||||
NSDictionary *event = @{
|
||||
@"target": self.reactTag,
|
||||
@"written": @(written),
|
||||
@"total": @(total),
|
||||
};
|
||||
[_eventDispatcher sendInputEventWithName:@"loadProgress" body:event];
|
||||
}
|
||||
};
|
||||
|
||||
void (^errorHandler)(NSString *errorDescription) = ^(NSString *errorDescription) {
|
||||
NSDictionary *event = @{
|
||||
@"target": self.reactTag,
|
||||
@"error": errorDescription,
|
||||
};
|
||||
[_eventDispatcher sendInputEventWithName:@"loadError" body:event];
|
||||
};
|
||||
|
||||
void (^loadEndHandler)(void) = ^(void) {
|
||||
NSDictionary *event = @{ @"target": self.reactTag };
|
||||
[_eventDispatcher sendInputEventWithName:@"loaded" body:event];
|
||||
};
|
||||
|
||||
if ([imageURL.pathExtension caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
|
||||
_downloadToken = [_imageDownloader downloadDataForURL:imageURL progressBlock:progressHandler block:^(NSData *data, NSError *error) {
|
||||
if (data) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (imageURL != self.imageURL) {
|
||||
// Image has changed
|
||||
return;
|
||||
}
|
||||
CAKeyframeAnimation *animation = RCTGIFImageWithData(data);
|
||||
self.layer.contentsScale = 1.0;
|
||||
self.layer.minificationFilter = kCAFilterLinear;
|
||||
self.layer.magnificationFilter = kCAFilterLinear;
|
||||
[self.layer addAnimation:animation forKey:@"contents"];
|
||||
loadEndHandler();
|
||||
});
|
||||
} else if (error) {
|
||||
errorHandler([error localizedDescription]);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
_downloadToken = [_imageDownloader downloadImageForURL:imageURL
|
||||
size:self.bounds.size
|
||||
scale:RCTScreenScale()
|
||||
resizeMode:self.contentMode
|
||||
tintColor:_tinted ? self.tintColor : nil
|
||||
backgroundColor:self.backgroundColor
|
||||
progressBlock:progressHandler
|
||||
block:^(UIImage *image, NSError *error) {
|
||||
if (image) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (imageURL != self.imageURL) {
|
||||
// Image has changed
|
||||
return;
|
||||
}
|
||||
[self.layer removeAnimationForKey:@"contents"];
|
||||
self.layer.contentsScale = image.scale;
|
||||
self.layer.contents = (__bridge id)image.CGImage;
|
||||
loadEndHandler();
|
||||
});
|
||||
} else if (error) {
|
||||
errorHandler([error localizedDescription]);
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setImageURL:(NSURL *)imageURL
|
||||
{
|
||||
[self setImageURL:imageURL resetToDefaultImageWhileLoading:YES];
|
||||
}
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow
|
||||
{
|
||||
[super willMoveToWindow:newWindow];
|
||||
if (newWindow != nil && _deferredImageURL) {
|
||||
// Immediately exit deferred mode and restore the imageURL that we saved when we went offscreen.
|
||||
[self setImageURL:_deferredImageURL resetToDefaultImageWhileLoading:YES];
|
||||
_deferredImageURL = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_enterDeferredModeIfNeededForSentinel:(NSUInteger)sentinel
|
||||
{
|
||||
if (self.window == nil && _deferSentinel == sentinel) {
|
||||
_deferred = YES;
|
||||
[_imageDownloader cancelDownload:_downloadToken];
|
||||
_downloadToken = nil;
|
||||
_deferredImageURL = _imageURL;
|
||||
_imageURL = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
[super didMoveToWindow];
|
||||
if (self.window == nil) {
|
||||
__weak RCTNetworkImageView *weakSelf = self;
|
||||
NSUInteger sentinelAtDispatchTime = ++_deferSentinel;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
|
||||
[weakSelf _enterDeferredModeIfNeededForSentinel:sentinelAtDispatchTime];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,15 +0,0 @@
|
|||
/**
|
||||
* 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 "RCTViewManager.h"
|
||||
|
||||
@interface RCTNetworkImageViewManager : RCTViewManager
|
||||
|
||||
@end
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* 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 "RCTNetworkImageViewManager.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTImageDownloader.h"
|
||||
#import "RCTNetworkImageView.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTNetworkImageViewManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
@synthesize methodQueue = _methodQueue;
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[RCTNetworkImageView alloc] initWithEventDispatcher:self.bridge.eventDispatcher imageDownloader:[RCTImageDownloader sharedInstance]];
|
||||
}
|
||||
|
||||
RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
|
||||
RCT_REMAP_VIEW_PROPERTY(src, imageURL, NSURL)
|
||||
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
|
||||
RCT_EXPORT_VIEW_PROPERTY(progressHandlerRegistered, BOOL)
|
||||
RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTNetworkImageView)
|
||||
{
|
||||
if (json) {
|
||||
view.tinted = YES;
|
||||
view.tintColor = [RCTConvert UIColor:json];
|
||||
} else {
|
||||
view.tinted = defaultView.tinted;
|
||||
view.tintColor = defaultView.tintColor;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *)customDirectEventTypes
|
||||
{
|
||||
return @{
|
||||
@"loadStart": @{ @"registrationName": @"onLoadStart" },
|
||||
@"loadProgress": @{ @"registrationName": @"onLoadProgress" },
|
||||
@"loaded": @{ @"registrationName": @"onLoadEnd" },
|
||||
@"loadError": @{ @"registrationName": @"onLoadError" },
|
||||
@"loadAbort": @{ @"registrationName": @"onLoadAbort" },
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* 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 "RCTStaticImageManager.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTStaticImage.h"
|
||||
|
||||
@implementation RCTStaticImageManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[RCTStaticImage alloc] init];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
|
||||
RCT_REMAP_VIEW_PROPERTY(imageTag, src, NSString)
|
||||
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
|
||||
RCT_EXPORT_VIEW_PROPERTY(src, NSString)
|
||||
RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTStaticImage)
|
||||
{
|
||||
if (json) {
|
||||
view.renderingMode = UIImageRenderingModeAlwaysTemplate;
|
||||
view.tintColor = [RCTConvert UIColor:json];
|
||||
} else {
|
||||
view.renderingMode = defaultView.renderingMode;
|
||||
view.tintColor = defaultView.tintColor;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -1408,8 +1408,13 @@ RCT_EXPORT_METHOD(clearJSResponder)
|
|||
for (RCTViewManager *manager in _viewManagers.allValues) {
|
||||
if (RCTClassOverridesInstanceMethod([manager class], @selector(customDirectEventTypes))) {
|
||||
NSDictionary *eventTypes = [manager customDirectEventTypes];
|
||||
for (NSString *eventName in eventTypes) {
|
||||
RCTAssert(!customDirectEventTypes[eventName], @"Event '%@' registered multiple times.", eventName);
|
||||
if (RCT_DEV) {
|
||||
for (NSString *eventName in eventTypes) {
|
||||
id eventType = customDirectEventTypes[eventName];
|
||||
RCTAssert(!eventType || [eventType isEqual:eventTypes[eventName]],
|
||||
@"Event '%@' registered multiple times with different "
|
||||
"properties.", eventName);
|
||||
}
|
||||
}
|
||||
[customDirectEventTypes addEntriesFromDictionary:eventTypes];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue