UIView+WebCache.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. /*
  2. * This file is part of the SDWebImage package.
  3. * (c) Olivier Poitrey <rs@dailymotion.com>
  4. *
  5. * For the full copyright and license information, please view the LICENSE
  6. * file that was distributed with this source code.
  7. */
  8. #import "UIView+WebCache.h"
  9. #import "objc/runtime.h"
  10. #import "UIView+WebCacheOperation.h"
  11. NSString * const SDWebImageInternalSetImageGroupKey = @"internalSetImageGroup";
  12. NSString * const SDWebImageExternalCustomManagerKey = @"externalCustomManager";
  13. const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
  14. static char imageURLKey;
  15. #if SD_UIKIT
  16. static char TAG_ACTIVITY_INDICATOR;
  17. static char TAG_ACTIVITY_STYLE;
  18. static char TAG_ACTIVITY_SHOW;
  19. #endif
  20. @implementation UIView (WebCache)
  21. - (nullable NSURL *)sd_imageURL {
  22. return objc_getAssociatedObject(self, &imageURLKey);
  23. }
  24. - (NSProgress *)sd_imageProgress {
  25. NSProgress *progress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
  26. if (!progress) {
  27. progress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
  28. self.sd_imageProgress = progress;
  29. }
  30. return progress;
  31. }
  32. - (void)setSd_imageProgress:(NSProgress *)sd_imageProgress {
  33. objc_setAssociatedObject(self, @selector(sd_imageProgress), sd_imageProgress, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  34. }
  35. - (void)sd_internalSetImageWithURL:(nullable NSURL *)url
  36. placeholderImage:(nullable UIImage *)placeholder
  37. options:(SDWebImageOptions)options
  38. operationKey:(nullable NSString *)operationKey
  39. setImageBlock:(nullable SDSetImageBlock)setImageBlock
  40. progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
  41. completed:(nullable SDExternalCompletionBlock)completedBlock {
  42. return [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options operationKey:operationKey setImageBlock:setImageBlock progress:progressBlock completed:completedBlock context:nil];
  43. }
  44. - (void)sd_internalSetImageWithURL:(nullable NSURL *)url
  45. placeholderImage:(nullable UIImage *)placeholder
  46. options:(SDWebImageOptions)options
  47. operationKey:(nullable NSString *)operationKey
  48. setImageBlock:(nullable SDSetImageBlock)setImageBlock
  49. progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
  50. completed:(nullable SDExternalCompletionBlock)completedBlock
  51. context:(nullable NSDictionary<NSString *, id> *)context {
  52. SDInternalSetImageBlock internalSetImageBlock;
  53. if (setImageBlock) {
  54. internalSetImageBlock = ^(UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  55. if (setImageBlock) {
  56. setImageBlock(image, imageData);
  57. }
  58. };
  59. }
  60. [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options operationKey:operationKey internalSetImageBlock:internalSetImageBlock progress:progressBlock completed:completedBlock context:context];
  61. }
  62. - (void)sd_internalSetImageWithURL:(nullable NSURL *)url
  63. placeholderImage:(nullable UIImage *)placeholder
  64. options:(SDWebImageOptions)options
  65. operationKey:(nullable NSString *)operationKey
  66. internalSetImageBlock:(nullable SDInternalSetImageBlock)setImageBlock
  67. progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
  68. completed:(nullable SDExternalCompletionBlock)completedBlock
  69. context:(nullable NSDictionary<NSString *, id> *)context {
  70. NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
  71. [self sd_cancelImageLoadOperationWithKey:validOperationKey];
  72. objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  73. dispatch_group_t group = context[SDWebImageInternalSetImageGroupKey];
  74. if (!(options & SDWebImageDelayPlaceholder)) {
  75. if (group) {
  76. dispatch_group_enter(group);
  77. }
  78. dispatch_main_async_safe(^{
  79. [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
  80. });
  81. }
  82. if (url) {
  83. #if SD_UIKIT
  84. // check if activityView is enabled or not
  85. if ([self sd_showActivityIndicatorView]) {
  86. [self sd_addActivityIndicator];
  87. }
  88. #endif
  89. // reset the progress
  90. self.sd_imageProgress.totalUnitCount = 0;
  91. self.sd_imageProgress.completedUnitCount = 0;
  92. SDWebImageManager *manager = [context objectForKey:SDWebImageExternalCustomManagerKey];
  93. if (!manager) {
  94. manager = [SDWebImageManager sharedManager];
  95. }
  96. __weak __typeof(self)wself = self;
  97. SDWebImageDownloaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
  98. wself.sd_imageProgress.totalUnitCount = expectedSize;
  99. wself.sd_imageProgress.completedUnitCount = receivedSize;
  100. if (progressBlock) {
  101. progressBlock(receivedSize, expectedSize, targetURL);
  102. }
  103. };
  104. id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
  105. __strong __typeof (wself) sself = wself;
  106. if (!sself) { return; }
  107. #if SD_UIKIT
  108. [sself sd_removeActivityIndicator];
  109. #endif
  110. // if the progress not been updated, mark it to complete state
  111. if (finished && !error && sself.sd_imageProgress.totalUnitCount == 0 && sself.sd_imageProgress.completedUnitCount == 0) {
  112. sself.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
  113. sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
  114. }
  115. BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
  116. BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
  117. (!image && !(options & SDWebImageDelayPlaceholder)));
  118. SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
  119. if (!sself) { return; }
  120. if (!shouldNotSetImage) {
  121. [sself sd_setNeedsLayout];
  122. }
  123. if (completedBlock && shouldCallCompletedBlock) {
  124. completedBlock(image, error, cacheType, url);
  125. }
  126. };
  127. // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
  128. // OR
  129. // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
  130. if (shouldNotSetImage) {
  131. dispatch_main_async_safe(callCompletedBlockClojure);
  132. return;
  133. }
  134. UIImage *targetImage = nil;
  135. NSData *targetData = nil;
  136. if (image) {
  137. // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
  138. targetImage = image;
  139. targetData = data;
  140. } else if (options & SDWebImageDelayPlaceholder) {
  141. // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
  142. targetImage = placeholder;
  143. targetData = nil;
  144. }
  145. #if SD_UIKIT || SD_MAC
  146. // check whether we should use the image transition
  147. SDWebImageTransition *transition = nil;
  148. if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
  149. transition = sself.sd_imageTransition;
  150. }
  151. #endif
  152. dispatch_main_async_safe(^{
  153. if (group) {
  154. dispatch_group_enter(group);
  155. }
  156. #if SD_UIKIT || SD_MAC
  157. [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
  158. #else
  159. [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
  160. #endif
  161. if (group) {
  162. // compatible code for FLAnimatedImage, because we assume completedBlock called after image was set. This will be removed in 5.x
  163. BOOL shouldUseGroup = [objc_getAssociatedObject(group, &SDWebImageInternalSetImageGroupKey) boolValue];
  164. if (shouldUseGroup) {
  165. dispatch_group_notify(group, dispatch_get_main_queue(), callCompletedBlockClojure);
  166. } else {
  167. callCompletedBlockClojure();
  168. }
  169. } else {
  170. callCompletedBlockClojure();
  171. }
  172. });
  173. }];
  174. [self sd_setImageLoadOperation:operation forKey:validOperationKey];
  175. } else {
  176. dispatch_main_async_safe(^{
  177. #if SD_UIKIT
  178. [self sd_removeActivityIndicator];
  179. #endif
  180. if (completedBlock) {
  181. NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
  182. completedBlock(nil, error, SDImageCacheTypeNone, url);
  183. }
  184. });
  185. }
  186. }
  187. - (void)sd_cancelCurrentImageLoad {
  188. [self sd_cancelImageLoadOperationWithKey:NSStringFromClass([self class])];
  189. }
  190. - (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDInternalSetImageBlock)setImageBlock cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL {
  191. #if SD_UIKIT || SD_MAC
  192. [self sd_setImage:image imageData:imageData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:nil cacheType:cacheType imageURL:imageURL];
  193. #else
  194. // watchOS does not support view transition. Simplify the logic
  195. if (setImageBlock) {
  196. setImageBlock(image, imageData, cacheType, imageURL);
  197. } else if ([self isKindOfClass:[UIImageView class]]) {
  198. UIImageView *imageView = (UIImageView *)self;
  199. [imageView setImage:image];
  200. }
  201. #endif
  202. }
  203. #if SD_UIKIT || SD_MAC
  204. - (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDInternalSetImageBlock)setImageBlock transition:(SDWebImageTransition *)transition cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL {
  205. UIView *view = self;
  206. SDInternalSetImageBlock finalSetImageBlock;
  207. if (setImageBlock) {
  208. finalSetImageBlock = setImageBlock;
  209. } else if ([view isKindOfClass:[UIImageView class]]) {
  210. UIImageView *imageView = (UIImageView *)view;
  211. finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData, SDImageCacheType setCacheType, NSURL *setImageURL) {
  212. imageView.image = setImage;
  213. };
  214. }
  215. #if SD_UIKIT
  216. else if ([view isKindOfClass:[UIButton class]]) {
  217. UIButton *button = (UIButton *)view;
  218. finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData, SDImageCacheType setCacheType, NSURL *setImageURL) {
  219. [button setImage:setImage forState:UIControlStateNormal];
  220. };
  221. }
  222. #endif
  223. if (transition) {
  224. #if SD_UIKIT
  225. [UIView transitionWithView:view duration:0 options:0 animations:^{
  226. // 0 duration to let UIKit render placeholder and prepares block
  227. if (transition.prepares) {
  228. transition.prepares(view, image, imageData, cacheType, imageURL);
  229. }
  230. } completion:^(BOOL finished) {
  231. [UIView transitionWithView:view duration:transition.duration options:transition.animationOptions animations:^{
  232. if (finalSetImageBlock && !transition.avoidAutoSetImage) {
  233. finalSetImageBlock(image, imageData, cacheType, imageURL);
  234. }
  235. if (transition.animations) {
  236. transition.animations(view, image);
  237. }
  238. } completion:transition.completion];
  239. }];
  240. #elif SD_MAC
  241. [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull prepareContext) {
  242. // 0 duration to let AppKit render placeholder and prepares block
  243. prepareContext.duration = 0;
  244. if (transition.prepares) {
  245. transition.prepares(view, image, imageData, cacheType, imageURL);
  246. }
  247. } completionHandler:^{
  248. [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
  249. context.duration = transition.duration;
  250. context.timingFunction = transition.timingFunction;
  251. context.allowsImplicitAnimation = (transition.animationOptions & SDWebImageAnimationOptionAllowsImplicitAnimation);
  252. if (finalSetImageBlock && !transition.avoidAutoSetImage) {
  253. finalSetImageBlock(image, imageData, cacheType, imageURL);
  254. }
  255. if (transition.animations) {
  256. transition.animations(view, image);
  257. }
  258. } completionHandler:^{
  259. if (transition.completion) {
  260. transition.completion(YES);
  261. }
  262. }];
  263. }];
  264. #endif
  265. } else {
  266. if (finalSetImageBlock) {
  267. finalSetImageBlock(image, imageData, cacheType, imageURL);
  268. }
  269. }
  270. }
  271. #endif
  272. - (void)sd_setNeedsLayout {
  273. #if SD_UIKIT
  274. [self setNeedsLayout];
  275. #elif SD_MAC
  276. [self setNeedsLayout:YES];
  277. #elif SD_WATCH
  278. // Do nothing because WatchKit automatically layout the view after property change
  279. #endif
  280. }
  281. #if SD_UIKIT || SD_MAC
  282. #pragma mark - Image Transition
  283. - (SDWebImageTransition *)sd_imageTransition {
  284. return objc_getAssociatedObject(self, @selector(sd_imageTransition));
  285. }
  286. - (void)setSd_imageTransition:(SDWebImageTransition *)sd_imageTransition {
  287. objc_setAssociatedObject(self, @selector(sd_imageTransition), sd_imageTransition, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  288. }
  289. #if SD_UIKIT
  290. #pragma mark - Activity indicator
  291. - (UIActivityIndicatorView *)activityIndicator {
  292. return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
  293. }
  294. - (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
  295. objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
  296. }
  297. - (void)sd_setShowActivityIndicatorView:(BOOL)show {
  298. objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, @(show), OBJC_ASSOCIATION_RETAIN);
  299. }
  300. - (BOOL)sd_showActivityIndicatorView {
  301. return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
  302. }
  303. - (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
  304. objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
  305. }
  306. - (int)sd_getIndicatorStyle{
  307. return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
  308. }
  309. - (void)sd_addActivityIndicator {
  310. dispatch_main_async_safe(^{
  311. if (!self.activityIndicator) {
  312. self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:[self sd_getIndicatorStyle]];
  313. self.activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
  314. [self addSubview:self.activityIndicator];
  315. [self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
  316. attribute:NSLayoutAttributeCenterX
  317. relatedBy:NSLayoutRelationEqual
  318. toItem:self
  319. attribute:NSLayoutAttributeCenterX
  320. multiplier:1.0
  321. constant:0.0]];
  322. [self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
  323. attribute:NSLayoutAttributeCenterY
  324. relatedBy:NSLayoutRelationEqual
  325. toItem:self
  326. attribute:NSLayoutAttributeCenterY
  327. multiplier:1.0
  328. constant:0.0]];
  329. }
  330. [self.activityIndicator startAnimating];
  331. });
  332. }
  333. - (void)sd_removeActivityIndicator {
  334. dispatch_main_async_safe(^{
  335. if (self.activityIndicator) {
  336. [self.activityIndicator removeFromSuperview];
  337. self.activityIndicator = nil;
  338. }
  339. });
  340. }
  341. #endif
  342. #endif
  343. @end