From 54619ab92d13f4c0d9f1871e27394b2f8b5f275d Mon Sep 17 00:00:00 2001 From: Fraser Scott-Morrison Date: Mon, 11 May 2015 10:01:38 +1200 Subject: [PATCH 1/5] Added methods for setting imageURL with success & failure blocks --- AsyncImageView/AsyncImageView.h | 6 + AsyncImageView/AsyncImageView.m | 331 ++++++++++++++++++++++---------- 2 files changed, 236 insertions(+), 101 deletions(-) diff --git a/AsyncImageView/AsyncImageView.h b/AsyncImageView/AsyncImageView.h index 7b1b64d..9d330ed 100755 --- a/AsyncImageView/AsyncImageView.h +++ b/AsyncImageView/AsyncImageView.h @@ -54,12 +54,15 @@ extern NSString *const AsyncImageErrorKey; @property (nonatomic, assign) NSUInteger concurrentLoads; @property (nonatomic, assign) NSTimeInterval loadingTimeout; +- (void)loadImageWithURL:(NSURL *)URL successBlock:(void (^)(UIImage *image))successBlock failureBlock:(void(^)(NSError *error))failureBlock; // New method for blocks by Fraser Scott-Morrison - (void)loadImageWithURL:(NSURL *)URL target:(id)target success:(SEL)success failure:(SEL)failure; - (void)loadImageWithURL:(NSURL *)URL target:(id)target action:(SEL)action; - (void)loadImageWithURL:(NSURL *)URL; +- (void)cancelLoadingURL:(NSURL *)URL actionBlock:(void (^)(UIImage *image))successBlock; // New method for blocks by Fraser Scott-Morrison - (void)cancelLoadingURL:(NSURL *)URL target:(id)target action:(SEL)action; - (void)cancelLoadingURL:(NSURL *)URL target:(id)target; - (void)cancelLoadingURL:(NSURL *)URL; +- (void)cancelLoadingImagesForTarget:(id)target actionBlock:(void (^)(UIImage *image))successBlock; // New method for blocks by Fraser Scott-Morrison - (void)cancelLoadingImagesForTarget:(id)target action:(SEL)action; - (void)cancelLoadingImagesForTarget:(id)target; - (NSURL *)URLForTarget:(id)target action:(SEL)action; @@ -72,6 +75,7 @@ extern NSString *const AsyncImageErrorKey; @property (nonatomic, strong) NSURL *imageURL; +- (void)setImageURL:(NSURL *)imageURL successBlock:(void (^)(UIImage *image))successBlock failureBlock:(void(^)(NSError *error))failureBlock; // New method for blocks by Fraser Scott-Morrison @end @@ -81,6 +85,8 @@ extern NSString *const AsyncImageErrorKey; @property (nonatomic, assign) UIActivityIndicatorViewStyle activityIndicatorStyle; @property (nonatomic, assign) NSTimeInterval crossfadeDuration; +- (void)setImageURL:(NSURL *)imageURL successBlock:(void (^)(UIImage *image))successBlock failureBlock:(void(^)(NSError *error))failureBlock; // New method for blocks by Fraser Scott-Morrison + @end diff --git a/AsyncImageView/AsyncImageView.m b/AsyncImageView/AsyncImageView.m index a366de9..6c1b590 100755 --- a/AsyncImageView/AsyncImageView.m +++ b/AsyncImageView/AsyncImageView.m @@ -60,14 +60,22 @@ @interface AsyncImageConnection : NSObject @property (nonatomic, strong) id target; @property (nonatomic, assign) SEL success; @property (nonatomic, assign) SEL failure; +@property (nonatomic, copy) void (^successBlock)(UIImage *image); +@property (nonatomic, copy) void (^failureBlock)(NSError *error); @property (nonatomic, getter = isLoading) BOOL loading; @property (nonatomic, getter = isCancelled) BOOL cancelled; +// New method for blocks by Fraser Scott-Morrison - (AsyncImageConnection *)initWithURL:(NSURL *)URL cache:(NSCache *)cache - target:(id)target - success:(SEL)success - failure:(SEL)failure; + successBlock:(void (^)(UIImage *image))successBlock + failureBlock:(void(^)(NSError *error))failureBlock; + +- (AsyncImageConnection *)initWithURL:(NSURL *)URL + cache:(NSCache *)cache + target:(id)target + success:(SEL)success + failure:(SEL)failure; - (void)start; - (void)cancel; @@ -80,9 +88,9 @@ @implementation AsyncImageConnection - (AsyncImageConnection *)initWithURL:(NSURL *)URL cache:(NSCache *)cache - target:(id)target - success:(SEL)success - failure:(SEL)failure + target:(id)target + success:(SEL)success + failure:(SEL)failure { if ((self = [self init])) { @@ -95,17 +103,33 @@ - (AsyncImageConnection *)initWithURL:(NSURL *)URL return self; } +// New method for blocks by Fraser Scott-Morrison +- (AsyncImageConnection *)initWithURL:(NSURL *)URL + cache:(NSCache *)cache + successBlock:(void (^)(UIImage *image))successBlock + failureBlock:(void(^)(NSError *error))failureBlock +{ + if ((self = [self init])) + { + self.URL = URL; + self.cache = cache; + self.successBlock = successBlock; + self.failureBlock = failureBlock; + } + return self; +} + - (UIImage *)cachedImage { if ([self.URL isFileURL]) - { - NSString *path = [[self.URL absoluteURL] path]; + { + NSString *path = [[self.URL absoluteURL] path]; NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; - if ([path hasPrefix:resourcePath]) - { - return [UIImage imageNamed:[path substringFromIndex:[resourcePath length]]]; - } - } + if ([path hasPrefix:resourcePath]) + { + return [UIImage imageNamed:[path substringFromIndex:[resourcePath length]]]; + } + } return [self.cache objectForKey:self.URL]; } @@ -116,18 +140,18 @@ - (BOOL)isInCache - (void)loadFailedWithError:(NSError *)error { - self.loading = NO; - self.cancelled = NO; + self.loading = NO; + self.cancelled = NO; [[NSNotificationCenter defaultCenter] postNotificationName:AsyncImageLoadDidFail object:self.target userInfo:@{AsyncImageURLKey: self.URL, - AsyncImageErrorKey: error}]; + AsyncImageErrorKey: error}]; } - (void)cacheImage:(UIImage *)image { - if (!self.cancelled) - { + if (!self.cancelled) + { if (image && self.URL) { BOOL storeInCache = YES; @@ -145,64 +169,64 @@ - (void)cacheImage:(UIImage *)image } } - NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: - image, AsyncImageImageKey, - self.URL, AsyncImageURLKey, - nil]; - if (self.cache) - { - userInfo[AsyncImageCacheKey] = self.cache; - } - - self.loading = NO; - [[NSNotificationCenter defaultCenter] postNotificationName:AsyncImageLoadDidFinish - object:self.target - userInfo:[userInfo copy]]; - } - else - { - self.loading = NO; - self.cancelled = NO; - } + NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + image, AsyncImageImageKey, + self.URL, AsyncImageURLKey, + nil]; + if (self.cache) + { + userInfo[AsyncImageCacheKey] = self.cache; + } + + self.loading = NO; + [[NSNotificationCenter defaultCenter] postNotificationName:AsyncImageLoadDidFinish + object:self.target + userInfo:[userInfo copy]]; + } + else + { + self.loading = NO; + self.cancelled = NO; + } } - (void)processDataInBackground:(NSData *)data { - @synchronized ([self class]) - { - if (!self.cancelled) - { + @synchronized ([self class]) + { + if (!self.cancelled) + { UIImage *image = [[UIImage alloc] initWithData:data]; - if (image) - { + if (image) + { //redraw to prevent deferred decompression UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale); [image drawAtPoint:CGPointZero]; image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - //add to cache (may be cached already but it doesn't matter) + //add to cache (may be cached already but it doesn't matter) [self performSelectorOnMainThread:@selector(cacheImage:) withObject:image waitUntilDone:YES]; - } - else - { + } + else + { @autoreleasepool { NSError *error = [NSError errorWithDomain:@"AsyncImageLoader" code:0 userInfo:@{NSLocalizedDescriptionKey: @"Invalid image data"}]; [self performSelectorOnMainThread:@selector(loadFailedWithError:) withObject:error waitUntilDone:YES]; - } - } - } - else - { - //clean up - [self performSelectorOnMainThread:@selector(cacheImage:) - withObject:nil - waitUntilDone:YES]; - } - } + } + } + } + else + { + //clean up + [self performSelectorOnMainThread:@selector(cacheImage:) + withObject:nil + waitUntilDone:YES]; + } + } } - (void)connection:(__unused NSURLConnection *)connection didReceiveResponse:(__unused NSURLResponse *)response @@ -236,10 +260,10 @@ - (void)start { return; } - - //begin loading - self.loading = YES; - self.cancelled = NO; + + //begin loading + self.loading = YES; + self.cancelled = NO; //check for nil URL if (self.URL == nil) @@ -249,7 +273,7 @@ - (void)start } //check for cached image - UIImage *image = [self cachedImage]; + UIImage *image = [self cachedImage]; if (image) { //add to cache (cached already but it doesn't matter) @@ -271,7 +295,7 @@ - (void)start - (void)cancel { - self.cancelled = YES; + self.cancelled = YES; [self.connection cancel]; self.connection = nil; self.data = nil; @@ -291,46 +315,46 @@ @implementation AsyncImageLoader + (AsyncImageLoader *)sharedLoader { - static AsyncImageLoader *sharedInstance = nil; - if (sharedInstance == nil) - { - sharedInstance = [(AsyncImageLoader *)[self alloc] init]; - } - return sharedInstance; + static AsyncImageLoader *sharedInstance = nil; + if (sharedInstance == nil) + { + sharedInstance = [(AsyncImageLoader *)[self alloc] init]; + } + return sharedInstance; } + (NSCache *)defaultCache { static NSCache *sharedCache = nil; - if (sharedCache == nil) - { - sharedCache = [[NSCache alloc] init]; + if (sharedCache == nil) + { + sharedCache = [[NSCache alloc] init]; [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification *note) { [sharedCache removeAllObjects]; }]; - } - return sharedCache; + } + return sharedCache; } - (AsyncImageLoader *)init { - if ((self = [super init])) - { + if ((self = [super init])) + { self.cache = [[self class] defaultCache]; _concurrentLoads = 2; _loadingTimeout = 60.0; - _connections = [[NSMutableArray alloc] init]; + _connections = [[NSMutableArray alloc] init]; [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(imageLoaded:) - name:AsyncImageLoadDidFinish - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(imageFailed:) - name:AsyncImageLoadDidFail - object:nil]; - } - return self; + selector:@selector(imageLoaded:) + name:AsyncImageLoadDidFinish + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(imageFailed:) + name:AsyncImageLoadDidFail + object:nil]; + } + return self; } - (void)updateQueue @@ -355,7 +379,7 @@ - (void)updateQueue } - (void)imageLoaded:(NSNotification *)notification -{ +{ //complete connections for URL NSURL *URL = (notification.userInfo)[AsyncImageURLKey]; for (NSInteger i = (NSInteger)[self.connections count] - 1; i >= 0; i--) @@ -367,8 +391,7 @@ - (void)imageLoaded:(NSNotification *)notification for (NSInteger j = i - 1; j >= 0; j--) { AsyncImageConnection *earlier = self.connections[(NSUInteger)j]; - if (earlier.target == connection.target && - earlier.success == connection.success) + if ((earlier.target == connection.target && earlier.success == connection.success) || (connection.successBlock && earlier.successBlock == connection.successBlock)) { [earlier cancel]; [self.connections removeObjectAtIndex:(NSUInteger)j]; @@ -380,8 +403,13 @@ - (void)imageLoaded:(NSNotification *)notification [connection cancel]; //perform action - UIImage *image = (notification.userInfo)[AsyncImageImageKey]; - ((void (*)(id, SEL, id, id))objc_msgSend)(connection.target, connection.success, image, connection.URL); + UIImage *image = (notification.userInfo)[AsyncImageImageKey]; + if (connection.target && connection.success) { + ((void (*)(id, SEL, id, id))objc_msgSend)(connection.target, connection.success, image, connection.URL); + } + else if (connection.successBlock) { + connection.successBlock(image); + } //remove from queue [self.connections removeObjectAtIndex:(NSUInteger)i]; @@ -410,6 +438,9 @@ - (void)imageFailed:(NSNotification *)notification NSError *error = (notification.userInfo)[AsyncImageErrorKey]; ((void (*)(id, SEL, id, id))objc_msgSend)(connection.target, connection.failure, error, URL); } + else if (connection.failureBlock) { + connection.failureBlock([NSError errorWithDomain:@"An image failed to load" code:0 userInfo:nil]); + } //remove from queue [self.connections removeObjectAtIndex:(NSUInteger)i]; @@ -420,6 +451,46 @@ - (void)imageFailed:(NSNotification *)notification [self updateQueue]; } +// New method for blocks by Fraser Scott-Morrison +- (void)loadImageWithURL:(NSURL *)URL successBlock:(void (^)(UIImage *image))successBlock failureBlock:(void(^)(NSError *error))failureBlock { + //check cache + UIImage *image = [self.cache objectForKey:URL]; + if (image) + { + [self cancelLoadingImagesForTarget:self actionBlock:successBlock]; + if (successBlock) + { + dispatch_async(dispatch_get_main_queue(), ^(void) { + successBlock(image); + }); + } + return; + } + + //create new connection + AsyncImageConnection *connection = [[AsyncImageConnection alloc] initWithURL:URL + cache:self.cache + successBlock:successBlock + failureBlock:failureBlock]; + BOOL added = NO; + for (NSUInteger i = 0; i < [self.connections count]; i++) + { + AsyncImageConnection *existingConnection = self.connections[i]; + if (!existingConnection.loading) + { + [self.connections insertObject:connection atIndex:i]; + added = YES; + break; + } + } + if (!added) + { + [self.connections addObject:connection]; + } + + [self updateQueue]; +} + - (void)loadImageWithURL:(NSURL *)URL target:(id)target success:(SEL)success failure:(SEL)failure { //check cache @@ -472,6 +543,20 @@ - (void)loadImageWithURL:(NSURL *)URL [self loadImageWithURL:URL target:nil success:NULL failure:NULL]; } +// New method for blocks by Fraser Scott-Morrison +- (void)cancelLoadingURL:(NSURL *)URL actionBlock:(void (^)(UIImage *image))successBlock +{ + for (NSInteger i = (NSInteger)[self.connections count] - 1; i >= 0; i--) + { + AsyncImageConnection *connection = self.connections[(NSUInteger)i]; + if ([connection.URL isEqual:URL] && connection.successBlock == successBlock) + { + [connection cancel]; + [self.connections removeObjectAtIndex:(NSUInteger)i]; + } + } +} + - (void)cancelLoadingURL:(NSURL *)URL target:(id)target action:(SEL)action { for (NSInteger i = (NSInteger)[self.connections count] - 1; i >= 0; i--) @@ -511,6 +596,19 @@ - (void)cancelLoadingURL:(NSURL *)URL } } +// New method for blocks by Fraser Scott-Morrison +- (void)cancelLoadingImagesForTarget:(id)target actionBlock:(void (^)(UIImage *image))successBlock +{ + for (NSInteger i = (NSInteger)[self.connections count] - 1; i >= 0; i--) + { + AsyncImageConnection *connection = self.connections[(NSUInteger)i]; + if (connection.target == target && connection.successBlock == successBlock) + { + [connection cancel]; + } + } +} + - (void)cancelLoadingImagesForTarget:(id)target action:(SEL)action { for (NSInteger i = (NSInteger)[self.connections count] - 1; i >= 0; i--) @@ -567,7 +665,7 @@ - (NSURL *)URLForTarget:(id)target - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end @@ -575,14 +673,20 @@ - (void)dealloc @implementation UIImageView(AsyncImageView) +// New method for blocks by Fraser Scott-Morrison +- (void)setImageURL:(NSURL *)imageURL successBlock:(void (^)(UIImage *image))successBlock failureBlock:(void(^)(NSError *error))failureBlock +{ + [[AsyncImageLoader sharedLoader] loadImageWithURL:imageURL successBlock:successBlock failureBlock:failureBlock]; +} + - (void)setImageURL:(NSURL *)imageURL { - [[AsyncImageLoader sharedLoader] loadImageWithURL:imageURL target:self action:@selector(setImage:)]; + [[AsyncImageLoader sharedLoader] loadImageWithURL:imageURL target:self action:@selector(setImage:)]; } - (NSURL *)imageURL { - return [[AsyncImageLoader sharedLoader] URLForTarget:self action:@selector(setImage:)]; + return [[AsyncImageLoader sharedLoader] URLForTarget:self action:@selector(setImage:)]; } @end @@ -599,9 +703,9 @@ @implementation AsyncImageView - (void)setUp { - self.showActivityIndicator = (self.image == nil); - self.activityIndicatorStyle = UIActivityIndicatorViewStyleGray; - self.crossfadeDuration = 0.4; + self.showActivityIndicator = (self.image == nil); + self.activityIndicatorStyle = UIActivityIndicatorViewStyleGray; + self.crossfadeDuration = 0.4; } - (id)initWithFrame:(CGRect)frame @@ -622,6 +726,31 @@ - (id)initWithCoder:(NSCoder *)aDecoder return self; } +// New method for blocks by Fraser Scott-Morrison +- (void)setImageURL:(NSURL *)imageURL successBlock:(void (^)(UIImage *image))successBlock failureBlock:(void(^)(NSError *error))failureBlock +{ + UIImage *image = [[AsyncImageLoader sharedLoader].cache objectForKey:imageURL]; + if (image) + { + self.image = image; + successBlock(image); + return; + } + [super setImageURL:imageURL successBlock:successBlock failureBlock:failureBlock]; + if (self.showActivityIndicator && !self.image && imageURL) + { + if (self.activityView == nil) + { + self.activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:self.activityIndicatorStyle]; + self.activityView.hidesWhenStopped = YES; + self.activityView.center = CGPointMake(self.bounds.size.width / 2.0f, self.bounds.size.height / 2.0f); + self.activityView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; + [self addSubview:self.activityView]; + } + [self.activityView startAnimating]; + } +} + - (void)setImageURL:(NSURL *)imageURL { UIImage *image = [[AsyncImageLoader sharedLoader].cache objectForKey:imageURL]; @@ -647,9 +776,9 @@ - (void)setImageURL:(NSURL *)imageURL - (void)setActivityIndicatorStyle:(UIActivityIndicatorViewStyle)style { - _activityIndicatorStyle = style; - [self.activityView removeFromSuperview]; - self.activityView = nil; + _activityIndicatorStyle = style; + [self.activityView removeFromSuperview]; + self.activityView = nil; } - (void)setImage:(UIImage *)image From 98079a85e2060778e67dbb06ee502a57b60c6600 Mon Sep 17 00:00:00 2001 From: Fraser Scott-Morrison Date: Mon, 11 May 2015 10:37:59 +1200 Subject: [PATCH 2/5] Created new pod: AsyncImageView-blocks --- AsyncImageView-blocks.podspec | 20 ++++++++++++++++++++ AsyncImageView.podspec | 20 -------------------- 2 files changed, 20 insertions(+), 20 deletions(-) create mode 100644 AsyncImageView-blocks.podspec delete mode 100644 AsyncImageView.podspec diff --git a/AsyncImageView-blocks.podspec b/AsyncImageView-blocks.podspec new file mode 100644 index 0000000..94c688f --- /dev/null +++ b/AsyncImageView-blocks.podspec @@ -0,0 +1,20 @@ +Pod::Spec.new do |s| +s.name = "AsyncImageView-blocks" +s.version = "1.5.1.1" +s.summary = "AsyncImageView is an extension of UIImageView for loading and displaying images asynchronously on iOS so that they do not lock up the UI." + +s.description = "AsyncImageView includes both a simple category on UIImageView for loading and displaying images asynchronously on iOS so that they do not lock up the UI, and a UIImageView subclass for more advanced features. AsyncImageView works with URLs so it can be used with either local or remote files. + +Loaded/downloaded images are cached in memory and are automatically cleaned up in the event of a memory warning. The AsyncImageView operates independently of the UIImage cache, but by default any images located in the root of the application bundle will be stored in the UIImage cache instead, avoiding any duplication of cached images. + +The library can also be used to load and cache images independently of a UIImageView as it provides direct access to the underlying loading and caching classes." + +s.homepage = "http://charcoaldesign.co.uk/source/cocoa#asyncimageview" +s.license = { :type => 'zlib', :file => 'LICENCE.md' } +s.author = { 'Fraser Scott-Morrison' => 'fraserscottmorrison@me.com' } +s.source = { :git => "https://github.com/IdleHandsApps/AsyncImageView.git", :tag => s.version.to_s } + +s.platform = :ios, '4.3' +s.source_files = 'AsyncImageView' +s.requires_arc = true +end diff --git a/AsyncImageView.podspec b/AsyncImageView.podspec deleted file mode 100644 index 56517ae..0000000 --- a/AsyncImageView.podspec +++ /dev/null @@ -1,20 +0,0 @@ -Pod::Spec.new do |s| - s.name = "AsyncImageView" - s.version = "1.5.1" - s.summary = "AsyncImageView is an extension of UIImageView for loading and displaying images asynchronously on iOS so that they do not lock up the UI." - - s.description = "AsyncImageView includes both a simple category on UIImageView for loading and displaying images asynchronously on iOS so that they do not lock up the UI, and a UIImageView subclass for more advanced features. AsyncImageView works with URLs so it can be used with either local or remote files. - - Loaded/downloaded images are cached in memory and are automatically cleaned up in the event of a memory warning. The AsyncImageView operates independently of the UIImage cache, but by default any images located in the root of the application bundle will be stored in the UIImage cache instead, avoiding any duplication of cached images. - - The library can also be used to load and cache images independently of a UIImageView as it provides direct access to the underlying loading and caching classes." - - s.homepage = "http://charcoaldesign.co.uk/source/cocoa#asyncimageview" - s.license = { :type => 'zlib', :file => 'LICENCE.md' } - s.author = { "Nick Lockwood" => "support@charcoaldesign.co.uk" } - s.source = { :git => "https://github.com/nicklockwood/AsyncImageView.git", :tag => "1.5.1" } - - s.platform = :ios, '4.3' - s.source_files = 'AsyncImageView' - s.requires_arc = true -end From 99314d354b0a6ba150368a1d2be4eff67ed08635 Mon Sep 17 00:00:00 2001 From: Fraser Scott-Morrison Date: Mon, 11 May 2015 13:30:43 +1200 Subject: [PATCH 3/5] Image scale now defaults @2x --- AsyncImageView/AsyncImageView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncImageView/AsyncImageView.m b/AsyncImageView/AsyncImageView.m index 6c1b590..addd025 100755 --- a/AsyncImageView/AsyncImageView.m +++ b/AsyncImageView/AsyncImageView.m @@ -196,7 +196,7 @@ - (void)processDataInBackground:(NSData *)data { if (!self.cancelled) { - UIImage *image = [[UIImage alloc] initWithData:data]; + UIImage *image = [[UIImage alloc] initWithData:data scale:2.0f]; if (image) { //redraw to prevent deferred decompression From c9fa63e57d42bb10f62045bc926fae5663775b32 Mon Sep 17 00:00:00 2001 From: Fraser Scott-Morrison Date: Mon, 11 May 2015 13:48:30 +1200 Subject: [PATCH 4/5] An Image with try to reload once if it fails --- AsyncImageView/AsyncImageView.m | 35 ++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/AsyncImageView/AsyncImageView.m b/AsyncImageView/AsyncImageView.m index addd025..d8e9ffa 100755 --- a/AsyncImageView/AsyncImageView.m +++ b/AsyncImageView/AsyncImageView.m @@ -58,6 +58,7 @@ @interface AsyncImageConnection : NSObject @property (nonatomic, strong) NSURL *URL; @property (nonatomic, strong) NSCache *cache; @property (nonatomic, strong) id target; +@property (nonatomic) BOOL doRetry; @property (nonatomic, assign) SEL success; @property (nonatomic, assign) SEL failure; @property (nonatomic, copy) void (^successBlock)(UIImage *image); @@ -432,18 +433,24 @@ - (void)imageFailed:(NSNotification *)notification //cancel connection (in case it's a duplicate) [connection cancel]; - //perform failure action - if (connection.failure) - { - NSError *error = (notification.userInfo)[AsyncImageErrorKey]; - ((void (*)(id, SEL, id, id))objc_msgSend)(connection.target, connection.failure, error, URL); - } - else if (connection.failureBlock) { - connection.failureBlock([NSError errorWithDomain:@"An image failed to load" code:0 userInfo:nil]); - } - //remove from queue [self.connections removeObjectAtIndex:(NSUInteger)i]; + + if (connection.doRetry) { + // retry once + [self loadImageWithURL:connection.URL doRetry:NO successBlock:connection.successBlock failureBlock:connection.failureBlock]; + } + else { + //perform failure action + if (connection.failure) + { + NSError *error = (notification.userInfo)[AsyncImageErrorKey]; + ((void (*)(id, SEL, id, id))objc_msgSend)(connection.target, connection.failure, error, URL); + } + else if (connection.failureBlock) { + connection.failureBlock([NSError errorWithDomain:@"An image failed to load" code:0 userInfo:nil]); + } + } } } @@ -451,8 +458,13 @@ - (void)imageFailed:(NSNotification *)notification [self updateQueue]; } -// New method for blocks by Fraser Scott-Morrison +// New method for blocks by Fraser Scott-Morrison. Will automatically try to reload image (once) if it fails - (void)loadImageWithURL:(NSURL *)URL successBlock:(void (^)(UIImage *image))successBlock failureBlock:(void(^)(NSError *error))failureBlock { + [self loadImageWithURL:URL doRetry:YES successBlock:successBlock failureBlock:failureBlock]; +} + +// New method for blocks by Fraser Scott-Morrison +- (void)loadImageWithURL:(NSURL *)URL doRetry:(BOOL)doRetry successBlock:(void (^)(UIImage *image))successBlock failureBlock:(void(^)(NSError *error))failureBlock { //check cache UIImage *image = [self.cache objectForKey:URL]; if (image) @@ -472,6 +484,7 @@ - (void)loadImageWithURL:(NSURL *)URL successBlock:(void (^)(UIImage *image))suc cache:self.cache successBlock:successBlock failureBlock:failureBlock]; + connection.doRetry = doRetry; BOOL added = NO; for (NSUInteger i = 0; i < [self.connections count]; i++) { From 80683e28f6ae8c3fa9444ac8f3596db460049714 Mon Sep 17 00:00:00 2001 From: Fraser Scott-Morrison Date: Mon, 11 May 2015 13:49:29 +1200 Subject: [PATCH 5/5] Bumping version to 1.5.1.2 --- AsyncImageView-blocks.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncImageView-blocks.podspec b/AsyncImageView-blocks.podspec index 94c688f..ba14177 100644 --- a/AsyncImageView-blocks.podspec +++ b/AsyncImageView-blocks.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "AsyncImageView-blocks" -s.version = "1.5.1.1" +s.version = "1.5.1.2" s.summary = "AsyncImageView is an extension of UIImageView for loading and displaying images asynchronously on iOS so that they do not lock up the UI." s.description = "AsyncImageView includes both a simple category on UIImageView for loading and displaying images asynchronously on iOS so that they do not lock up the UI, and a UIImageView subclass for more advanced features. AsyncImageView works with URLs so it can be used with either local or remote files.