Add a NSProgress property represent the image loading progress, this allow user add KVO on it for complicated logic

This commit is contained in:
DreamPiggy 2018-01-13 21:35:51 +08:00
parent 09e90b5215
commit 1ef45bace1
3 changed files with 78 additions and 6 deletions

View File

@ -20,6 +20,10 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageInternalSetImageGroupKey;
A SDWebImageManager instance to control the image download and cache process using in UIImageView+WebCache category and likes. If not provided, use the shared manager (SDWebImageManager)
*/
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageExternalCustomManagerKey;
/**
The value specify that the image progress unit count cannot be determined because the progressBlock is not been called. This value is actually 1
*/
FOUNDATION_EXPORT const int64_t SDWebImageProgressUnitCountUnknown;
typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable imageData);
@ -28,11 +32,19 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima
/**
* Get the current image URL.
*
* Note that because of the limitations of categories this property can get out of sync
* if you use setImage: directly.
* @note Note that because of the limitations of categories this property can get out of sync if you use setImage: directly.
*/
- (nullable NSURL *)sd_imageURL;
/**
* The current image loading progress associated to the view. The unit count is the received size and excepted size of download
* The `completedUnitCount` will be reset to 0 after a new image loading start. And `completedUnitCount` will be `SDWebImageProgressUnitCountUnknown` if the progressBlock not been called but the image loading success to mark the progress finished.
* @note You can use Key-Value Observing on the progress, but you should take care that the change to progress is from a background queue(the same as progressBlock). If you want to using KVO and update the UI, make sure to dispatch on the main queue. And it's recommand to use some KVO libs like KVOController because it's more safe and easy to use.
* @note The getter will create a progress instance if the value is nil. You can also set a custom progress instance and let it been updated during image loading
* @note Note that because of the limitations of categories this property can get out of sync if you update the progress directly.
*/
@property (nonatomic, strong, null_resettable) NSProgress *sd_imageProgress;
/**
* Set the imageView `image` with an `url` and optionally a placeholder image.
*

View File

@ -16,6 +16,8 @@
NSString * const SDWebImageInternalSetImageGroupKey = @"internalSetImageGroup";
NSString * const SDWebImageExternalCustomManagerKey = @"externalCustomManager";
const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
static char imageURLKey;
#if SD_UIKIT
@ -30,6 +32,19 @@ static char TAG_ACTIVITY_SHOW;
return objc_getAssociatedObject(self, &imageURLKey);
}
- (NSProgress *)sd_imageProgress {
NSProgress *progress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
if (!progress) {
progress = [[NSProgress alloc] init];
self.sd_imageProgress = progress;
}
return progress;
}
- (void)setSd_imageProgress:(NSProgress *)sd_imageProgress {
objc_setAssociatedObject(self, @selector(sd_imageProgress), sd_imageProgress, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
@ -68,6 +83,9 @@ static char TAG_ACTIVITY_SHOW;
[self sd_addActivityIndicator];
}
// reset the progress
self.sd_imageProgress.completedUnitCount = 0;
SDWebImageManager *manager;
if ([context valueForKey:SDWebImageExternalCustomManagerKey]) {
manager = (SDWebImageManager *)[context valueForKey:SDWebImageExternalCustomManagerKey];
@ -76,10 +94,21 @@ static char TAG_ACTIVITY_SHOW;
}
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
SDWebImageDownloaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
wself.sd_imageProgress.totalUnitCount = expectedSize;
wself.sd_imageProgress.completedUnitCount = receivedSize;
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
[sself sd_removeActivityIndicator];
if (!sself) { return; }
[sself sd_removeActivityIndicator];
// if the progress not been updated, mark it to complete state
if (finished && !error && sself.sd_imageProgress.completedUnitCount == 0) {
sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));

View File

@ -13,8 +13,7 @@
#import <SDWebImage/MKAnnotationView+WebCache.h>
#import <SDWebImage/UIButton+WebCache.h>
#import <SDWebImage/FLAnimatedImageView+WebCache.h>
@import FLAnimatedImage;
#import <SDWebImage/UIView+WebCache.h>
@interface SDCategoriesTests : SDTestCase
@ -139,4 +138,36 @@
[self waitForExpectationsWithCommonTimeout];
}
- (void)testUIViewImageProgressWorkWithKVO {
XCTestExpectation *expectation = [self expectationWithDescription:@"UIView imageProgressKVO failed"];
UIView *view = [[UIView alloc] init];
NSURL *originalImageURL = [NSURL URLWithString:kTestJpegURL];
[view.sd_imageProgress addObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNew context:_cmd];
// Clear the disk cache to force download from network
[[SDImageCache sharedImageCache] removeImageForKey:kTestJpegURL withCompletion:^{
[view sd_internalSetImageWithURL:originalImageURL placeholderImage:nil options:0 operationKey:nil setImageBlock:nil progress:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
expect(view.sd_imageProgress.fractionCompleted).equal(1.0);
[expectation fulfill];
}];
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:^(NSError * _Nullable error) {
[view.sd_imageProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) context:_cmd];
}];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == @selector(testUIViewImageProgressWorkWithKVO)) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(fractionCompleted))]) {
NSProgress *progress = object;
NSNumber *completedValue = change[NSKeyValueChangeNewKey];
expect(progress.fractionCompleted).equal(completedValue.doubleValue);
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
@end