diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js index b886bf861..82721993f 100644 --- a/Examples/UIExplorer/ImageExample.js +++ b/Examples/UIExplorer/ImageExample.js @@ -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({ 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} ; } diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 63534af3e..e1fc6df2f 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -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 ( + ); - 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 ; } }); @@ -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; diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj index 8ecabbafd..3eabd148e 100644 --- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj +++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj @@ -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 = ""; }; 03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTaskWrapper.m; sourceTree = ""; }; - 1304D5A71AA8C4A30002E2BE /* RCTStaticImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImage.h; sourceTree = ""; }; - 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImage.m; sourceTree = ""; }; - 1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImageManager.h; sourceTree = ""; }; - 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImageManager.m; sourceTree = ""; }; + 1304D5A71AA8C4A30002E2BE /* RCTImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageView.h; sourceTree = ""; }; + 1304D5A81AA8C4A30002E2BE /* RCTImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageView.m; sourceTree = ""; }; + 1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageViewManager.h; sourceTree = ""; }; + 1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageViewManager.m; sourceTree = ""; }; 1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTGIFImage.h; sourceTree = ""; }; 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = ""; }; 1345A8371B26592900583190 /* RCTImageRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageRequestHandler.h; sourceTree = ""; }; @@ -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 = ""; }; 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = ""; }; - 58B5118B1A9E6BD600147676 /* RCTNetworkImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworkImageView.h; sourceTree = ""; }; - 58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkImageView.m; sourceTree = ""; }; - 58B5118D1A9E6BD600147676 /* RCTNetworkImageViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworkImageViewManager.h; sourceTree = ""; }; - 58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkImageViewManager.m; sourceTree = ""; }; /* 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; diff --git a/Libraries/Image/RCTImageDownloader.h b/Libraries/Image/RCTImageDownloader.h index 43bb9a69d..44ad1cde3 100644 --- a/Libraries/Image/RCTImageDownloader.h +++ b/Libraries/Image/RCTImageDownloader.h @@ -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 diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index f32d895cb..6cec0f478 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -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 diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h index 4337836fd..25fdb1b30 100644 --- a/Libraries/Image/RCTImageLoader.h +++ b/Libraries/Image/RCTImageLoader.h @@ -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 diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 69d98a60a..405b4907b 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -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 diff --git a/Libraries/Image/RCTStaticImage.h b/Libraries/Image/RCTImageView.h similarity index 72% rename from Libraries/Image/RCTStaticImage.h rename to Libraries/Image/RCTImageView.h index c8f46a302..fff7c96a0 100644 --- a/Libraries/Image/RCTStaticImage.h +++ b/Libraries/Image/RCTImageView.h @@ -9,9 +9,14 @@ #import -@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; diff --git a/Libraries/Image/RCTStaticImage.m b/Libraries/Image/RCTImageView.m similarity index 59% rename from Libraries/Image/RCTStaticImage.m rename to Libraries/Image/RCTImageView.m index 0e9d4b608..8773aebb7 100644 --- a/Libraries/Image/RCTStaticImage.m +++ b/Libraries/Image/RCTImageView.m @@ -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() diff --git a/Libraries/Image/RCTStaticImageManager.h b/Libraries/Image/RCTImageViewManager.h similarity index 87% rename from Libraries/Image/RCTStaticImageManager.h rename to Libraries/Image/RCTImageViewManager.h index b02f9fe11..4e8d3fac4 100644 --- a/Libraries/Image/RCTStaticImageManager.h +++ b/Libraries/Image/RCTImageViewManager.h @@ -9,6 +9,6 @@ #import "RCTViewManager.h" -@interface RCTStaticImageManager : RCTViewManager +@interface RCTImageViewManager : RCTViewManager @end diff --git a/Libraries/Image/RCTImageViewManager.m b/Libraries/Image/RCTImageViewManager.m new file mode 100644 index 000000000..28f93466a --- /dev/null +++ b/Libraries/Image/RCTImageViewManager.m @@ -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 + +#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 diff --git a/Libraries/Image/RCTNetworkImageView.h b/Libraries/Image/RCTNetworkImageView.h deleted file mode 100644 index 6dd73e9aa..000000000 --- a/Libraries/Image/RCTNetworkImageView.h +++ /dev/null @@ -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 - -@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 diff --git a/Libraries/Image/RCTNetworkImageView.m b/Libraries/Image/RCTNetworkImageView.m deleted file mode 100644 index 20d297b46..000000000 --- a/Libraries/Image/RCTNetworkImageView.m +++ /dev/null @@ -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 diff --git a/Libraries/Image/RCTNetworkImageViewManager.h b/Libraries/Image/RCTNetworkImageViewManager.h deleted file mode 100644 index 3176ce896..000000000 --- a/Libraries/Image/RCTNetworkImageViewManager.h +++ /dev/null @@ -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 - diff --git a/Libraries/Image/RCTNetworkImageViewManager.m b/Libraries/Image/RCTNetworkImageViewManager.m deleted file mode 100644 index f42ef48f1..000000000 --- a/Libraries/Image/RCTNetworkImageViewManager.m +++ /dev/null @@ -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 diff --git a/Libraries/Image/RCTStaticImageManager.m b/Libraries/Image/RCTStaticImageManager.m deleted file mode 100644 index 7b3fb16db..000000000 --- a/Libraries/Image/RCTStaticImageManager.m +++ /dev/null @@ -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 - -#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 diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 724ace6e6..d1af57705 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -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]; }