Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Image] Add onLoadStart, onLoadProgress, onLoadError events to Image component #1318

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 29 additions & 1 deletion Libraries/Image/Image.ios.js 100644 → 100755
Expand Up @@ -104,7 +104,33 @@ var Image = React.createClass({
*
* {nativeEvent: { layout: {x, y, width, height}}}.
*/
onLayout: PropTypes.func,
onLayout: PropTypes.func,
/**
* Invoked on load start
*/
onLoadStart: PropTypes.func,
/**
* Invoked on download progress with
*
* {nativeEvent: { written, total}}.
*/
onLoadProgress: PropTypes.func,
/**
* Invoked on load abort
*/
onLoadAbort: PropTypes.func,
/**
* Invoked on load error
*
* {nativeEvent: { error}}.
*/
onLoadError: PropTypes.func,
/**
* Invoked on load end
*
*/
onLoaded: PropTypes.func

},

statics: {
Expand Down Expand Up @@ -161,6 +187,7 @@ var Image = React.createClass({
if (this.props.defaultSource) {
nativeProps.defaultImageSrc = this.props.defaultSource.uri;
}
nativeProps.progressHandlerRegistered = isNetwork && this.props.onLoadProgress;
return <RawImage {...nativeProps} />;
}
});
Expand All @@ -178,6 +205,7 @@ var nativeOnlyProps = {
src: true,
defaultImageSrc: true,
imageTag: true,
progressHandlerRegistered: true
};
if (__DEV__) {
verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps);
Expand Down
26 changes: 26 additions & 0 deletions Libraries/Image/RCTDownloadTaskWrapper.h
@@ -0,0 +1,26 @@
/**
* 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 <Foundation/Foundation.h>

typedef void (^RCTDataCompletionBlock)(NSURLResponse *response, NSData *data, NSError *error);
typedef void (^RCTDataProgressBlock)(int64_t written, int64_t total);

@interface RCTDownloadTaskWrapper : NSObject <NSURLSessionDownloadDelegate>

@property (copy, nonatomic) RCTDataCompletionBlock completionBlock;
@property (copy, nonatomic) RCTDataProgressBlock progressBlock;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration delegateQueue:(NSOperationQueue *)delegateQueue;

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration delegateQueue:(NSOperationQueue *)delegateQueue;

- (NSURLSessionDownloadTask *)downloadData:(NSURL *) url progressBlock:(RCTDataProgressBlock)progressBlock completionBlock:(RCTDataCompletionBlock)completionBlock;

@property(nonatomic,assign) id<NSURLSessionDownloadDelegate> delegate;

@end
74 changes: 74 additions & 0 deletions Libraries/Image/RCTDownloadTaskWrapper.m
@@ -0,0 +1,74 @@
/**
* 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 "RCTDownloadTaskWrapper.h"

@implementation RCTDownloadTaskWrapper
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
  NSURLSession *_URLSession;
}

{
NSURLSession *_URLSession;
}

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration delegateQueue:(NSOperationQueue *)delegateQueue
{
if ((self = [super init])) {
_URLSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
}

return self;
}

- (NSURLSessionDownloadTask *)downloadData:(NSURL *)url
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration delegateQueue:(NSOperationQueue *)delegateQueue
{
  if ((self = [super init])) {
    _URLSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
  }

  return self;
}

progressBlock:(RCTDataProgressBlock)progressBlock
completionBlock:(RCTDataCompletionBlock)completionBlock
{
self.completionBlock = completionBlock;
self.progressBlock = progressBlock;
NSURLSessionDownloadTask *task = [_URLSession downloadTaskWithURL:url completionHandler:nil];
[task resume];
return task;
}

#pragma mark - NSURLSessionTaskDelegate methods

- (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
NSData *data = [NSData dataWithContentsOfURL:location];
dispatch_async(dispatch_get_main_queue(), ^{
self.completionBlock(downloadTask.response, data, nil);
});

}

- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)didWriteData
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
{
dispatch_async(dispatch_get_main_queue(), ^{
if (self.progressBlock != nil) {
self.progressBlock(totalBytesWritten, totalBytesExpectedToWrite);
}
});
}

- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
self.completionBlock(NULL, NULL, error);
});
}
}

@end
6 changes: 6 additions & 0 deletions Libraries/Image/RCTImage.xcodeproj/project.pbxproj 100644 → 100755
Expand Up @@ -7,6 +7,7 @@
objects = {

/* 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 */; };
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; };
Expand All @@ -32,6 +33,8 @@
/* End PBXCopyFilesBuildPhase section */

/* 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>"; };
Expand Down Expand Up @@ -69,6 +72,8 @@
58B511541A9E6B3D00147676 = {
isa = PBXGroup;
children = (
03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */,
03559E7D1B064D3A00730281 /* RCTDownloadTaskWrapper.h */,
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */,
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */,
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */,
Expand Down Expand Up @@ -168,6 +173,7 @@
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 */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
3 changes: 3 additions & 0 deletions Libraries/Image/RCTImageDownloader.h 100644 → 100755
Expand Up @@ -8,6 +8,7 @@
*/

#import <UIKit/UIKit.h>
#import "RCTDownloadTaskWrapper.h"

typedef void (^RCTDataDownloadBlock)(NSData *data, NSError *error);
typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
Expand All @@ -22,6 +23,7 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
* the main thread. Returns a token that can be used to cancel the download.
*/
- (id)downloadDataForURL:(NSURL *)url
progressBlock:(RCTDataProgressBlock)progressBlock
block:(RCTDataDownloadBlock)block;

/**
Expand All @@ -35,6 +37,7 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
backgroundColor:(UIColor *)backgroundColor
progressBlock:(RCTDataProgressBlock)progressBlock
block:(RCTImageDownloadBlock)block;

/**
Expand Down
27 changes: 16 additions & 11 deletions Libraries/Image/RCTImageDownloader.m 100644 → 100755
Expand Up @@ -11,6 +11,7 @@

#import "RCTLog.h"
#import "RCTUtils.h"
#import "RCTDownloadTaskWrapper.h"

typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *error);

Expand Down Expand Up @@ -45,12 +46,14 @@ - (instancetype)init
return self;
}

- (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block
- (id)_downloadDataForURL:(NSURL *)url progressBlock:progressBlock block:(RCTCachedDataDownloadBlock)block
{
NSString *cacheKey = url.absoluteString;

__block BOOL cancelled = NO;
__block NSURLSessionDataTask *task = nil;
__block NSURLSessionDownloadTask *task = nil;
RCTDownloadTaskWrapper *_downloadTaskWrapper = [[RCTDownloadTaskWrapper alloc]
initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegateQueue:nil];

dispatch_block_t cancel = ^{
cancelled = YES;
Expand Down Expand Up @@ -84,40 +87,41 @@ - (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block
}
});
};
NSURLRequest *request = [NSURLRequest requestWithURL:url];
task = [_downloadTaskWrapper downloadData:url progressBlock:progressBlock
completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {

task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!cancelled) {
runBlocks(NO, data, error);
}

if (response) {
RCTImageDownloader *strongSelf = weakSelf;
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed];
[strongSelf->_cache storeCachedResponse:cachedResponse forDataTask:task];
[strongSelf->_cache storeCachedResponse:cachedResponse forRequest:request];
}
task = nil;
}];

[_cache getCachedResponseForDataTask:task completionHandler:^(NSCachedURLResponse *cachedResponse) {
NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest:request];
if (cancelled) {
return;
}

if (cachedResponse) {
runBlocks(YES, cachedResponse.data, nil);
} else {
[task resume];
}
}];

}
});

return [cancel copy];
}

- (id)downloadDataForURL:(NSURL *)url block:(RCTDataDownloadBlock)block
- (id)downloadDataForURL:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock block:(RCTDataDownloadBlock)block
{
return [self _downloadDataForURL:url block:^(BOOL cached, NSData *data, NSError *error) {
return [self _downloadDataForURL:url progressBlock:progressBlock block:^(BOOL cached, NSData *data, NSError *error) {
block(data, error);
}];
}
Expand All @@ -127,9 +131,10 @@ - (id)downloadImageForURL:(NSURL *)url
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
backgroundColor:(UIColor *)backgroundColor
progressBlock:(RCTDataProgressBlock)progressBlock
block:(RCTImageDownloadBlock)block
{
return [self downloadDataForURL:url block:^(NSData *data, NSError *error) {
return [self downloadDataForURL:url progressBlock:progressBlock block:^(NSData *data, NSError *error) {
if (!data || error) {
block(nil, error);
return;
Expand Down Expand Up @@ -262,4 +267,4 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
return (CGRect){CGPointZero, destSize};
}
}
}
2 changes: 1 addition & 1 deletion Libraries/Image/RCTImageLoader.m 100644 → 100755
Expand Up @@ -121,7 +121,7 @@ + (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error,
RCTDispatchCallbackOnMainQueue(callback, RCTErrorWithMessage(errorMessage), nil);
return;
}
[[RCTImageDownloader sharedInstance] downloadDataForURL:url block:^(NSData *data, NSError *error) {
[[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:nil block:^(NSData *data, NSError *error) {
if (error) {
RCTDispatchCallbackOnMainQueue(callback, error, nil);
} else {
Expand Down
4 changes: 3 additions & 1 deletion Libraries/Image/RCTNetworkImageView.h 100644 → 100755
Expand Up @@ -10,10 +10,12 @@
#import <UIKit/UIKit.h>

@class RCTImageDownloader;
@class RCTEventDispatcher;

@interface RCTNetworkImageView : UIView

- (instancetype)initWithImageDownloader:(RCTImageDownloader *)imageDownloader NS_DESIGNATED_INITIALIZER;
- (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,
Expand Down