/*
|
* This file is part of the SDWebImage package.
|
* (c) Olivier Poitrey <rs@dailymotion.com>
|
*
|
* For the full copyright and license information, please view the LICENSE
|
* file that was distributed with this source code.
|
*/
|
|
#import "UIView+WebCache.h"
|
#import "objc/runtime.h"
|
#import "UIView+WebCacheOperation.h"
|
|
NSString * const SDWebImageInternalSetImageGroupKey = @"internalSetImageGroup";
|
NSString * const SDWebImageExternalCustomManagerKey = @"externalCustomManager";
|
|
const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
|
|
static char imageURLKey;
|
|
#if SD_UIKIT
|
static char TAG_ACTIVITY_INDICATOR;
|
static char TAG_ACTIVITY_STYLE;
|
static char TAG_ACTIVITY_SHOW;
|
#endif
|
|
@implementation UIView (WebCache)
|
|
- (nullable NSURL *)sd_imageURL {
|
return objc_getAssociatedObject(self, &imageURLKey);
|
}
|
|
- (NSProgress *)sd_imageProgress {
|
NSProgress *progress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
|
if (!progress) {
|
progress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
|
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
|
operationKey:(nullable NSString *)operationKey
|
setImageBlock:(nullable SDSetImageBlock)setImageBlock
|
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
completed:(nullable SDExternalCompletionBlock)completedBlock {
|
return [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options operationKey:operationKey setImageBlock:setImageBlock progress:progressBlock completed:completedBlock context:nil];
|
}
|
|
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
|
placeholderImage:(nullable UIImage *)placeholder
|
options:(SDWebImageOptions)options
|
operationKey:(nullable NSString *)operationKey
|
setImageBlock:(nullable SDSetImageBlock)setImageBlock
|
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
completed:(nullable SDExternalCompletionBlock)completedBlock
|
context:(nullable NSDictionary<NSString *, id> *)context {
|
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
|
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
|
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
if (!(options & SDWebImageDelayPlaceholder)) {
|
if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
|
dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
|
dispatch_group_enter(group);
|
}
|
dispatch_main_async_safe(^{
|
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
|
});
|
}
|
|
if (url) {
|
#if SD_UIKIT
|
// check if activityView is enabled or not
|
if ([self sd_showActivityIndicatorView]) {
|
[self sd_addActivityIndicator];
|
}
|
#endif
|
|
// reset the progress
|
self.sd_imageProgress.totalUnitCount = 0;
|
self.sd_imageProgress.completedUnitCount = 0;
|
|
SDWebImageManager *manager;
|
if ([context valueForKey:SDWebImageExternalCustomManagerKey]) {
|
manager = (SDWebImageManager *)[context valueForKey:SDWebImageExternalCustomManagerKey];
|
} else {
|
manager = [SDWebImageManager sharedManager];
|
}
|
|
__weak __typeof(self)wself = self;
|
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;
|
if (!sself) { return; }
|
#if SD_UIKIT
|
[sself sd_removeActivityIndicator];
|
#endif
|
// if the progress not been updated, mark it to complete state
|
if (finished && !error && sself.sd_imageProgress.totalUnitCount == 0 && sself.sd_imageProgress.completedUnitCount == 0) {
|
sself.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
|
sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
|
}
|
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
|
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
|
(!image && !(options & SDWebImageDelayPlaceholder)));
|
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
|
if (!sself) { return; }
|
if (!shouldNotSetImage) {
|
[sself sd_setNeedsLayout];
|
}
|
if (completedBlock && shouldCallCompletedBlock) {
|
completedBlock(image, error, cacheType, url);
|
}
|
};
|
|
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
|
// OR
|
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
|
if (shouldNotSetImage) {
|
dispatch_main_async_safe(callCompletedBlockClojure);
|
return;
|
}
|
|
UIImage *targetImage = nil;
|
NSData *targetData = nil;
|
if (image) {
|
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
|
targetImage = image;
|
targetData = data;
|
} else if (options & SDWebImageDelayPlaceholder) {
|
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
|
targetImage = placeholder;
|
targetData = nil;
|
}
|
|
#if SD_UIKIT || SD_MAC
|
// check whether we should use the image transition
|
SDWebImageTransition *transition = nil;
|
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
|
transition = sself.sd_imageTransition;
|
}
|
#endif
|
if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
|
dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
|
dispatch_group_enter(group);
|
dispatch_main_async_safe(^{
|
#if SD_UIKIT || SD_MAC
|
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
|
#else
|
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
|
#endif
|
});
|
// ensure completion block is called after custom setImage process finish
|
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
callCompletedBlockClojure();
|
});
|
} else {
|
dispatch_main_async_safe(^{
|
#if SD_UIKIT || SD_MAC
|
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
|
#else
|
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
|
#endif
|
callCompletedBlockClojure();
|
});
|
}
|
}];
|
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
|
} else {
|
dispatch_main_async_safe(^{
|
#if SD_UIKIT
|
[self sd_removeActivityIndicator];
|
#endif
|
if (completedBlock) {
|
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
|
completedBlock(nil, error, SDImageCacheTypeNone, url);
|
}
|
});
|
}
|
}
|
|
- (void)sd_cancelCurrentImageLoad {
|
[self sd_cancelImageLoadOperationWithKey:NSStringFromClass([self class])];
|
}
|
|
- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock {
|
#if SD_UIKIT || SD_MAC
|
[self sd_setImage:image imageData:imageData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:nil cacheType:0 imageURL:nil];
|
#else
|
// watchOS does not support view transition. Simplify the logic
|
if (setImageBlock) {
|
setImageBlock(image, imageData);
|
} else if ([self isKindOfClass:[UIImageView class]]) {
|
UIImageView *imageView = (UIImageView *)self;
|
[imageView setImage:image];
|
}
|
#endif
|
}
|
|
#if SD_UIKIT || SD_MAC
|
- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock transition:(SDWebImageTransition *)transition cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL {
|
UIView *view = self;
|
SDSetImageBlock finalSetImageBlock;
|
if (setImageBlock) {
|
finalSetImageBlock = setImageBlock;
|
} else if ([view isKindOfClass:[UIImageView class]]) {
|
UIImageView *imageView = (UIImageView *)view;
|
finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData) {
|
imageView.image = setImage;
|
};
|
}
|
#if SD_UIKIT
|
else if ([view isKindOfClass:[UIButton class]]) {
|
UIButton *button = (UIButton *)view;
|
finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData){
|
[button setImage:setImage forState:UIControlStateNormal];
|
};
|
}
|
#endif
|
|
if (transition) {
|
#if SD_UIKIT
|
[UIView transitionWithView:view duration:0 options:0 animations:^{
|
// 0 duration to let UIKit render placeholder and prepares block
|
if (transition.prepares) {
|
transition.prepares(view, image, imageData, cacheType, imageURL);
|
}
|
} completion:^(BOOL finished) {
|
[UIView transitionWithView:view duration:transition.duration options:transition.animationOptions animations:^{
|
if (finalSetImageBlock && !transition.avoidAutoSetImage) {
|
finalSetImageBlock(image, imageData);
|
}
|
if (transition.animations) {
|
transition.animations(view, image);
|
}
|
} completion:transition.completion];
|
}];
|
#elif SD_MAC
|
[NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull prepareContext) {
|
// 0 duration to let AppKit render placeholder and prepares block
|
prepareContext.duration = 0;
|
if (transition.prepares) {
|
transition.prepares(view, image, imageData, cacheType, imageURL);
|
}
|
} completionHandler:^{
|
[NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
|
context.duration = transition.duration;
|
context.timingFunction = transition.timingFunction;
|
context.allowsImplicitAnimation = (transition.animationOptions & SDWebImageAnimationOptionAllowsImplicitAnimation);
|
if (finalSetImageBlock && !transition.avoidAutoSetImage) {
|
finalSetImageBlock(image, imageData);
|
}
|
if (transition.animations) {
|
transition.animations(view, image);
|
}
|
} completionHandler:^{
|
if (transition.completion) {
|
transition.completion(YES);
|
}
|
}];
|
}];
|
#endif
|
} else {
|
if (finalSetImageBlock) {
|
finalSetImageBlock(image, imageData);
|
}
|
}
|
}
|
#endif
|
|
- (void)sd_setNeedsLayout {
|
#if SD_UIKIT
|
[self setNeedsLayout];
|
#elif SD_MAC
|
[self setNeedsLayout:YES];
|
#elif SD_WATCH
|
// Do nothing because WatchKit automatically layout the view after property change
|
#endif
|
}
|
|
#if SD_UIKIT || SD_MAC
|
|
#pragma mark - Image Transition
|
- (SDWebImageTransition *)sd_imageTransition {
|
return objc_getAssociatedObject(self, @selector(sd_imageTransition));
|
}
|
|
- (void)setSd_imageTransition:(SDWebImageTransition *)sd_imageTransition {
|
objc_setAssociatedObject(self, @selector(sd_imageTransition), sd_imageTransition, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
}
|
|
#if SD_UIKIT
|
|
#pragma mark - Activity indicator
|
- (UIActivityIndicatorView *)activityIndicator {
|
return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
|
}
|
|
- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
|
objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
|
}
|
|
- (void)sd_setShowActivityIndicatorView:(BOOL)show {
|
objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, @(show), OBJC_ASSOCIATION_RETAIN);
|
}
|
|
- (BOOL)sd_showActivityIndicatorView {
|
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
|
}
|
|
- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
|
objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
|
}
|
|
- (int)sd_getIndicatorStyle{
|
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
|
}
|
|
- (void)sd_addActivityIndicator {
|
dispatch_main_async_safe(^{
|
if (!self.activityIndicator) {
|
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:[self sd_getIndicatorStyle]];
|
self.activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self addSubview:self.activityIndicator];
|
|
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
|
attribute:NSLayoutAttributeCenterX
|
relatedBy:NSLayoutRelationEqual
|
toItem:self
|
attribute:NSLayoutAttributeCenterX
|
multiplier:1.0
|
constant:0.0]];
|
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
|
attribute:NSLayoutAttributeCenterY
|
relatedBy:NSLayoutRelationEqual
|
toItem:self
|
attribute:NSLayoutAttributeCenterY
|
multiplier:1.0
|
constant:0.0]];
|
}
|
[self.activityIndicator startAnimating];
|
});
|
}
|
|
- (void)sd_removeActivityIndicator {
|
dispatch_main_async_safe(^{
|
if (self.activityIndicator) {
|
[self.activityIndicator removeFromSuperview];
|
self.activityIndicator = nil;
|
}
|
});
|
}
|
|
#endif
|
|
#endif
|
|
@end
|