From 829d54ff036f287da919bb5463076942cf5b8dc9 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 17 Jan 2018 15:20:38 +0800 Subject: [PATCH 1/9] Add the image transition argument for all UIView+WebCache, make this easy for user to do some fade transition. It also reuse the current setImageBlock and make it easy to customize --- .../SDWebImage Demo/MasterViewController.m | 1 + Examples/SDWebImage OSX Demo/ViewController.m | 4 +- SDWebImage.xcodeproj/project.pbxproj | 28 ++++ SDWebImage/SDWebImageManager.h | 6 +- SDWebImage/SDWebImageTransition.h | 97 +++++++++++++ SDWebImage/SDWebImageTransition.m | 137 ++++++++++++++++++ SDWebImage/UIView+WebCache.h | 11 +- SDWebImage/UIView+WebCache.m | 95 ++++++++++-- WebImage/SDWebImage.h | 1 + 9 files changed, 364 insertions(+), 16 deletions(-) create mode 100644 SDWebImage/SDWebImageTransition.h create mode 100644 SDWebImage/SDWebImageTransition.m diff --git a/Examples/SDWebImage Demo/MasterViewController.m b/Examples/SDWebImage Demo/MasterViewController.m index 0ec2372b..5e2b37bb 100644 --- a/Examples/SDWebImage Demo/MasterViewController.m +++ b/Examples/SDWebImage Demo/MasterViewController.m @@ -117,6 +117,7 @@ MyCustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[MyCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; + cell.customImageView.sd_imageTransition = SDWebImageTransition.fadeTransition; } [cell.customImageView sd_setShowActivityIndicatorView:YES]; diff --git a/Examples/SDWebImage OSX Demo/ViewController.m b/Examples/SDWebImage OSX Demo/ViewController.m index 2d7e8774..6a8ec69f 100644 --- a/Examples/SDWebImage OSX Demo/ViewController.m +++ b/Examples/SDWebImage OSX Demo/ViewController.m @@ -33,7 +33,9 @@ [self.imageView1 sd_setImageWithURL:[NSURL URLWithString:@"http://assets.sbnation.com/assets/2512203/dogflops.gif"]]; [self.imageView2 sd_setImageWithURL:[NSURL URLWithString:@"http://www.ioncannon.net/wp-content/uploads/2011/06/test2.webp"]]; [self.imageView3 sd_setImageWithURL:[NSURL URLWithString:@"http://littlesvr.ca/apng/images/SteamEngine.webp"]]; - [self.imageView4 sd_setImageWithURL:[NSURL URLWithString:@"http://s3.amazonaws.com/fast-image-cache/demo-images/FICDDemoImage001.jpg"]]; + self.imageView4.wantsLayer = YES; + self.imageView4.sd_imageTransition = SDWebImageTransition.fadeTransition; + [self.imageView4 sd_setImageWithURL:[NSURL URLWithString:@"http://s3.amazonaws.com/fast-image-cache/demo-images/FICDDemoImage001.jpg"] placeholderImage:nil options:SDWebImageForceTransition]; } - (void)setRepresentedObject:(id)representedObject { diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index fac78404..270e37b9 100644 --- a/SDWebImage.xcodeproj/project.pbxproj +++ b/SDWebImage.xcodeproj/project.pbxproj @@ -307,6 +307,18 @@ 323F8C1D1F38EF770092B609 /* muxread.c in Sources */ = {isa = PBXBuildFile; fileRef = 323F8B3D1F38EF770092B609 /* muxread.c */; }; 323F8C1E1F38EF770092B609 /* muxread.c in Sources */ = {isa = PBXBuildFile; fileRef = 323F8B3D1F38EF770092B609 /* muxread.c */; }; 323F8C1F1F38EF770092B609 /* muxread.c in Sources */ = {isa = PBXBuildFile; fileRef = 323F8B3D1F38EF770092B609 /* muxread.c */; }; + 325312C8200F09910046BF1E /* SDWebImageTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = 325312C6200F09910046BF1E /* SDWebImageTransition.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 325312C9200F09910046BF1E /* SDWebImageTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = 325312C6200F09910046BF1E /* SDWebImageTransition.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 325312CA200F09910046BF1E /* SDWebImageTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = 325312C6200F09910046BF1E /* SDWebImageTransition.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 325312CB200F09910046BF1E /* SDWebImageTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = 325312C6200F09910046BF1E /* SDWebImageTransition.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 325312CC200F09910046BF1E /* SDWebImageTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = 325312C6200F09910046BF1E /* SDWebImageTransition.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 325312CD200F09910046BF1E /* SDWebImageTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = 325312C6200F09910046BF1E /* SDWebImageTransition.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 325312CE200F09910046BF1E /* SDWebImageTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 325312C7200F09910046BF1E /* SDWebImageTransition.m */; }; + 325312CF200F09910046BF1E /* SDWebImageTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 325312C7200F09910046BF1E /* SDWebImageTransition.m */; }; + 325312D0200F09910046BF1E /* SDWebImageTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 325312C7200F09910046BF1E /* SDWebImageTransition.m */; }; + 325312D1200F09910046BF1E /* SDWebImageTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 325312C7200F09910046BF1E /* SDWebImageTransition.m */; }; + 325312D2200F09910046BF1E /* SDWebImageTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 325312C7200F09910046BF1E /* SDWebImageTransition.m */; }; + 325312D3200F09910046BF1E /* SDWebImageTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 325312C7200F09910046BF1E /* SDWebImageTransition.m */; }; 3290FA041FA478AF0047D20C /* SDWebImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 3290FA021FA478AF0047D20C /* SDWebImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3290FA051FA478AF0047D20C /* SDWebImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 3290FA021FA478AF0047D20C /* SDWebImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3290FA061FA478AF0047D20C /* SDWebImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 3290FA021FA478AF0047D20C /* SDWebImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1318,6 +1330,8 @@ 323F8B3B1F38EF770092B609 /* muxi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = muxi.h; sourceTree = ""; }; 323F8B3C1F38EF770092B609 /* muxinternal.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = muxinternal.c; sourceTree = ""; }; 323F8B3D1F38EF770092B609 /* muxread.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = muxread.c; sourceTree = ""; }; + 325312C6200F09910046BF1E /* SDWebImageTransition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTransition.h; sourceTree = ""; }; + 325312C7200F09910046BF1E /* SDWebImageTransition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTransition.m; sourceTree = ""; }; 3290FA021FA478AF0047D20C /* SDWebImageFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageFrame.h; sourceTree = ""; }; 3290FA031FA478AF0047D20C /* SDWebImageFrame.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageFrame.m; sourceTree = ""; }; 32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageCoderHelper.h; sourceTree = ""; }; @@ -1797,6 +1811,8 @@ 53922D8F148C56230056699D /* SDWebImageManager.m */, 53922D91148C56230056699D /* SDWebImagePrefetcher.h */, 53922D92148C56230056699D /* SDWebImagePrefetcher.m */, + 325312C6200F09910046BF1E /* SDWebImageTransition.h */, + 325312C7200F09910046BF1E /* SDWebImageTransition.m */, ); name = Utils; sourceTree = ""; @@ -2024,6 +2040,7 @@ 80377C481F2F666300F89830 /* bit_reader_utils.h in Headers */, 80377C511F2F666300F89830 /* huffman_encode_utils.h in Headers */, 00733A6B1BC4880E00A5A117 /* NSData+ImageContentType.h in Headers */, + 325312CB200F09910046BF1E /* SDWebImageTransition.h in Headers */, 323F8C111F38EF770092B609 /* muxi.h in Headers */, 80377EC41F2F66D500F89830 /* vp8li_dec.h in Headers */, 00733A6A1BC4880E00A5A117 /* SDWebImagePrefetcher.h in Headers */, @@ -2093,6 +2110,7 @@ 80377EA11F2F66D400F89830 /* vp8_dec.h in Headers */, 80377C271F2F666300F89830 /* rescaler_utils.h in Headers */, 323F8B511F38EF770092B609 /* backward_references_enc.h in Headers */, + 325312C9200F09910046BF1E /* SDWebImageTransition.h in Headers */, 43A918651D8308FE00B3925F /* SDImageCacheConfig.h in Headers */, 4314D1741D0E0E3B004B36C9 /* types.h in Headers */, 4314D1761D0E0E3B004B36C9 /* decode.h in Headers */, @@ -2169,6 +2187,7 @@ 431BB6E91D06D2C1006A3455 /* SDWebImageDownloaderOperation.h in Headers */, 80377ED41F2F66D500F89830 /* vp8li_dec.h in Headers */, 431BB6EB1D06D2C1006A3455 /* UIView+WebCacheOperation.h in Headers */, + 325312CC200F09910046BF1E /* SDWebImageTransition.h in Headers */, 80377C6D1F2F666400F89830 /* huffman_utils.h in Headers */, 80377C731F2F666400F89830 /* random_utils.h in Headers */, 431BB6EE1D06D2C1006A3455 /* NSData+ImageContentType.h in Headers */, @@ -2271,6 +2290,7 @@ 321E60C31F38E91700405457 /* UIImage+ForceDecode.h in Headers */, 80377E561F2F66A800F89830 /* lossless_common.h in Headers */, 4397D2E91D0DDD8C00BB2784 /* UIImage+WebP.h in Headers */, + 325312CD200F09910046BF1E /* SDWebImageTransition.h in Headers */, 4397D2EA1D0DDD8C00BB2784 /* UIImage+GIF.h in Headers */, 321E60B51F38E90100405457 /* SDWebImageWebPCoder.h in Headers */, 4397D2EB1D0DDD8C00BB2784 /* NSData+ImageContentType.h in Headers */, @@ -2333,6 +2353,7 @@ 80377C2E1F2F666300F89830 /* bit_reader_utils.h in Headers */, 80377C371F2F666300F89830 /* huffman_encode_utils.h in Headers */, 4A2CAE2F1AB4BB7500B6BC39 /* UIImage+MultiFormat.h in Headers */, + 325312CA200F09910046BF1E /* SDWebImageTransition.h in Headers */, 323F8C101F38EF770092B609 /* muxi.h in Headers */, 80377EB41F2F66D400F89830 /* vp8li_dec.h in Headers */, 4A2CAE1A1AB4BB6400B6BC39 /* SDWebImageOperation.h in Headers */, @@ -2373,6 +2394,7 @@ 431738BF1CDFC2660008FEB9 /* encode.h in Headers */, 53761316155AD0D5005750A4 /* SDImageCache.h in Headers */, 323F8C0E1F38EF770092B609 /* muxi.h in Headers */, + 325312C8200F09910046BF1E /* SDWebImageTransition.h in Headers */, 321E60A21F38E8F600405457 /* SDWebImageGIFCoder.h in Headers */, 5D5B9142188EE8DD006D06BD /* NSData+ImageContentType.h in Headers */, 80377BFE1F2F665300F89830 /* color_cache_utils.h in Headers */, @@ -2722,6 +2744,7 @@ 323F8B991F38EF770092B609 /* near_lossless_enc.c in Sources */, 80377DE81F2F66A700F89830 /* yuv_mips_dsp_r2.c in Sources */, 80377EC31F2F66D500F89830 /* vp8l_dec.c in Sources */, + 325312D1200F09910046BF1E /* SDWebImageTransition.m in Sources */, 321E609D1F38E8ED00405457 /* SDWebImageImageIOCoder.m in Sources */, 323F8B9F1F38EF770092B609 /* picture_csp_enc.c in Sources */, 43C892A31D9D6DDD0022038D /* demux.c in Sources */, @@ -2883,6 +2906,7 @@ 323F8B451F38EF770092B609 /* analysis_enc.c in Sources */, 80377C261F2F666300F89830 /* rescaler_utils.c in Sources */, 323F8BBB1F38EF770092B609 /* predictor_enc.c in Sources */, + 325312CF200F09910046BF1E /* SDWebImageTransition.m in Sources */, 80377D2F1F2F66A700F89830 /* dec_msa.c in Sources */, 323F8C151F38EF770092B609 /* muxinternal.c in Sources */, 80377D571F2F66A700F89830 /* rescaler_sse2.c in Sources */, @@ -3026,6 +3050,7 @@ 80377C6A1F2F666400F89830 /* huffman_encode_utils.c in Sources */, 323F8B481F38EF770092B609 /* analysis_enc.c in Sources */, 80377DFE1F2F66A800F89830 /* dec_msa.c in Sources */, + 325312D2200F09910046BF1E /* SDWebImageTransition.m in Sources */, 323F8BBE1F38EF770092B609 /* predictor_enc.c in Sources */, 80377E261F2F66A800F89830 /* rescaler_sse2.c in Sources */, 323F8C181F38EF770092B609 /* muxinternal.c in Sources */, @@ -3214,6 +3239,7 @@ 43A918701D8308FE00B3925F /* SDImageCacheConfig.m in Sources */, 80377E4B1F2F66A800F89830 /* enc_mips32.c in Sources */, 4397D2AB1D0DDD8C00BB2784 /* UIView+WebCacheOperation.m in Sources */, + 325312D3200F09910046BF1E /* SDWebImageTransition.m in Sources */, 80377E391F2F66A800F89830 /* argb.c in Sources */, 4369C2831D9807EC007E863A /* UIView+WebCache.m in Sources */, 80377E611F2F66A800F89830 /* lossless_sse2.c in Sources */, @@ -3300,6 +3326,7 @@ 323F8B981F38EF770092B609 /* near_lossless_enc.c in Sources */, 80377D6F1F2F66A700F89830 /* cost.c in Sources */, 80377EB31F2F66D400F89830 /* vp8l_dec.c in Sources */, + 325312D0200F09910046BF1E /* SDWebImageTransition.m in Sources */, 321E609C1F38E8ED00405457 /* SDWebImageImageIOCoder.m in Sources */, 323F8B9E1F38EF770092B609 /* picture_csp_enc.c in Sources */, 80377D9E1F2F66A700F89830 /* upsampling_mips_dsp_r2.c in Sources */, @@ -3447,6 +3474,7 @@ 80377CE51F2F66A100F89830 /* cost.c in Sources */, 80377E931F2F66D000F89830 /* vp8l_dec.c in Sources */, 321E609A1F38E8ED00405457 /* SDWebImageImageIOCoder.m in Sources */, + 325312CE200F09910046BF1E /* SDWebImageTransition.m in Sources */, 323F8B9C1F38EF770092B609 /* picture_csp_enc.c in Sources */, 80377D141F2F66A100F89830 /* upsampling_mips_dsp_r2.c in Sources */, 80377D191F2F66A100F89830 /* yuv_mips_dsp_r2.c in Sources */, diff --git a/SDWebImage/SDWebImageManager.h b/SDWebImage/SDWebImageManager.h index dbe84fa9..6c83529b 100644 --- a/SDWebImage/SDWebImageManager.h +++ b/SDWebImage/SDWebImageManager.h @@ -111,7 +111,11 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { /** * By default, when the cache missed, the image is download from the network. This flag can prevent network to load from cache only. */ - SDWebImageFromCacheOnly = 1 << 15 + SDWebImageFromCacheOnly = 1 << 15, + /** + * By default, when you use `SDWebImageTransition` to do some view transition after the image load finished, this transition is only applied for image download from the network. This mask can force to apply view transition for memory and disk cache as well. + */ + SDWebImageForceTransition = 1 << 16 }; typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL); diff --git a/SDWebImage/SDWebImageTransition.h b/SDWebImage/SDWebImageTransition.h new file mode 100644 index 00000000..dff9ef70 --- /dev/null +++ b/SDWebImage/SDWebImageTransition.h @@ -0,0 +1,97 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDWebImageCompat.h" + +#if SD_UIKIT || SD_MAC +#import "SDImageCache.h" + +// for UIKit(iOS & tvOS), we use `+[UIView transitionWithView:duration:options:animations:completion]` for transition animations. +// for AppKit(macOS), we use `+[NSAnimationContext runAnimationGroup:completionHandler:]` for transition animations. You can call `+[NSAnimationContext currentContext]` to grab the context during animations block. +// These transition are provided for basic usage. If you need complicated animation, consider to directly use Core Animation or use `SDWebImageAvoidAutoSetImage` and implement your own after image load finished. + +#if SD_UIKIT +typedef UIViewAnimationOptions SDWebImageAnimationOptions; +#else +typedef NS_OPTIONS(NSUInteger, SDWebImageAnimationOptions) { + SDWebImageAnimationOptionAllowsImplicitAnimation = 1 << 0, // specify `allowsImplicitAnimation` for the `NSAnimationContext` +}; +#endif + +typedef void (^SDWebImageTransitionPreparesBlock)(__kindof UIView * _Nonnull view, UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL); +typedef void (^SDWebImageTransitionAnimationsBlock)(__kindof UIView * _Nonnull view, UIImage * _Nullable image); +typedef void (^SDWebImageTransitionCompletionBlock)(BOOL finished); + +@interface SDWebImageTransition : NSObject + +/** + By default, we set the image to the view at the beginning of the animtions. You can disable this and provide custom set image process + */ +@property (nonatomic, assign) BOOL avoidAutoSetImage; +/** + The duration of the transition animation, measured in seconds. Defaults to 0.5. + */ +@property (nonatomic, assign) NSTimeInterval duration; +/** + The timing function used for all animations within this transition animation (macOS). + */ +@property (nonatomic, strong, nullable) CAMediaTimingFunction *timingFunction NS_AVAILABLE_MAC(10_7); +/** + A mask of options indicating how you want to perform the animations. + */ +@property (nonatomic, assign) SDWebImageAnimationOptions animationOptions; +/** + A block object to be executed before the animation sequence starts. + */ +@property (nonatomic, copy, nullable) SDWebImageTransitionPreparesBlock prepares; +/** + A block object that contains the changes you want to make to the specified view. + */ +@property (nonatomic, copy, nullable) SDWebImageTransitionAnimationsBlock animations; +/** + A block object to be executed when the animation sequence ends. + */ +@property (nonatomic, copy, nullable) SDWebImageTransitionCompletionBlock completion; + +@end + +// Convenience way to use transition. Remember to specify the duration +// for UIKit, these transition just use the correspond `animationOptions` +// for AppKit, these transition use Core Animation in `animations`. So your view must be layer-backed. Set `wantsLayer = YES` before you apply it. + +@interface SDWebImageTransition (Conveniences) + +// class property is available in Xcode 8. We will drop the Xcode 7.3 support in 5.x +#if __has_feature(objc_class_property) +/// Fade transition. +@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *fadeTransition; +/// Flip from left transition. +@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *flipFromLeftTransition; +/// Flip from right transition. +@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *flipFromRightTransition; +/// Flip from top transition. +@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *flipFromTopTransition; +/// Flip from bottom transition. +@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *flipFromBottomTransition; +/// Curl up transition. +@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *curlUpTransition; +/// Curl down transition. +@property (nonatomic, class, nonnull, readonly) SDWebImageTransition *curlDownTransition; +#else ++ (nonnull instancetype)fadeTransition; ++ (nonnull instancetype)flipFromLeftTransition; ++ (nonnull instancetype)flipFromRightTransition; ++ (nonnull instancetype)flipFromTopTransition; ++ (nonnull instancetype)flipFromBottomTransition; ++ (nonnull instancetype)curlUpTransition; ++ (nonnull instancetype)curlDownTransition; +#endif + +@end + +#endif diff --git a/SDWebImage/SDWebImageTransition.m b/SDWebImage/SDWebImageTransition.m new file mode 100644 index 00000000..122b982f --- /dev/null +++ b/SDWebImage/SDWebImageTransition.m @@ -0,0 +1,137 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDWebImageTransition.h" + +#if SD_UIKIT || SD_MAC + +#if SD_MAC +#import +#endif + +@implementation SDWebImageTransition + +- (instancetype)init { + self = [super init]; + if (self) { + self.duration = 0.5; + } + return self; +} + +@end + +@implementation SDWebImageTransition (Conveniences) + ++ (SDWebImageTransition *)fadeTransition { + SDWebImageTransition *transition = [SDWebImageTransition new]; +#if SD_UIKIT + transition.animationOptions = UIViewAnimationOptionTransitionCrossDissolve; +#else + transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) { + CATransition *trans = [CATransition animation]; + trans.type = kCATransitionFade; + [view.layer addAnimation:trans forKey:kCATransition]; + }; +#endif + return transition; +} + ++ (SDWebImageTransition *)flipFromLeftTransition { + SDWebImageTransition *transition = [SDWebImageTransition new]; +#if SD_UIKIT + transition.animationOptions = UIViewAnimationOptionTransitionFlipFromLeft; +#else + transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) { + CATransition *trans = [CATransition animation]; + trans.type = kCATransitionPush; + trans.subtype = kCATransitionFromLeft; + [view.layer addAnimation:trans forKey:kCATransition]; + }; +#endif + return transition; +} + ++ (SDWebImageTransition *)flipFromRightTransition { + SDWebImageTransition *transition = [SDWebImageTransition new]; +#if SD_UIKIT + transition.animationOptions = UIViewAnimationOptionTransitionFlipFromRight; +#else + transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) { + CATransition *trans = [CATransition animation]; + trans.type = kCATransitionPush; + trans.subtype = kCATransitionFromRight; + [view.layer addAnimation:trans forKey:kCATransition]; + }; +#endif + return transition; +} + ++ (SDWebImageTransition *)flipFromTopTransition { + SDWebImageTransition *transition = [SDWebImageTransition new]; +#if SD_UIKIT + transition.animationOptions = UIViewAnimationOptionTransitionFlipFromTop; +#else + transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) { + CATransition *trans = [CATransition animation]; + trans.type = kCATransitionPush; + trans.subtype = kCATransitionFromTop; + [view.layer addAnimation:trans forKey:kCATransition]; + }; +#endif + return transition; +} + ++ (SDWebImageTransition *)flipFromBottomTransition { + SDWebImageTransition *transition = [SDWebImageTransition new]; +#if SD_UIKIT + transition.animationOptions = UIViewAnimationOptionTransitionFlipFromBottom; +#else + transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) { + CATransition *trans = [CATransition animation]; + trans.type = kCATransitionPush; + trans.subtype = kCATransitionFromBottom; + [view.layer addAnimation:trans forKey:kCATransition]; + }; +#endif + return transition; +} + ++ (SDWebImageTransition *)curlUpTransition { + SDWebImageTransition *transition = [SDWebImageTransition new]; +#if SD_UIKIT + transition.animationOptions = UIViewAnimationOptionTransitionCurlUp; +#else + transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) { + CATransition *trans = [CATransition animation]; + trans.type = kCATransitionReveal; + trans.subtype = kCATransitionFromTop; + [view.layer addAnimation:trans forKey:kCATransition]; + }; +#endif + return transition; +} + ++ (SDWebImageTransition *)curlDownTransition { + SDWebImageTransition *transition = [SDWebImageTransition new]; +#if SD_UIKIT + transition.animationOptions = UIViewAnimationOptionTransitionCurlDown; +#else + transition.animations = ^(__kindof NSView * _Nonnull view, NSImage * _Nullable image) { + CATransition *trans = [CATransition animation]; + trans.type = kCATransitionReveal; + trans.subtype = kCATransitionFromBottom; + [view.layer addAnimation:trans forKey:kCATransition]; + }; +#endif + return transition; +} + +@end + +#endif diff --git a/SDWebImage/UIView+WebCache.h b/SDWebImage/UIView+WebCache.h index 941d0f3a..d89452d8 100644 --- a/SDWebImage/UIView+WebCache.h +++ b/SDWebImage/UIView+WebCache.h @@ -11,6 +11,7 @@ #if SD_UIKIT || SD_MAC #import "SDWebImageManager.h" +#import "SDWebImageTransition.h" /** A Dispatch group to maintain setImageBlock and completionBlock. This key should be used only internally and may be changed in the future. (dispatch_group_t) @@ -97,13 +98,21 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima setImageBlock:(nullable SDSetImageBlock)setImageBlock progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock - context:(nullable NSDictionary *)context; + context:(nullable NSDictionary *)context; /** * Cancel the current image load */ - (void)sd_cancelCurrentImageLoad; +#pragma mark - Image Transition + +/** + The image transition when image load finished. See `SDWebImageTransition`. + If you specify nil, do not do transition. Defautls to nil. + */ +@property (nonatomic, strong, nullable) SDWebImageTransition *sd_imageTransition; + #if SD_UIKIT #pragma mark - Activity indicator diff --git a/SDWebImage/UIView+WebCache.m b/SDWebImage/UIView+WebCache.m index d4cc8984..99e11b2a 100644 --- a/SDWebImage/UIView+WebCache.m +++ b/SDWebImage/UIView+WebCache.m @@ -62,7 +62,7 @@ static char TAG_ACTIVITY_SHOW; setImageBlock:(nullable SDSetImageBlock)setImageBlock progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock - context:(nullable NSDictionary *)context { + context:(nullable NSDictionary *)context { NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]); [self sd_cancelImageLoadOperationWithKey:validOperationKey]; objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC); @@ -144,11 +144,16 @@ static char TAG_ACTIVITY_SHOW; targetData = nil; } + // check whether we should use the image transition + SDWebImageTransition *transition = nil; + if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) { + transition = sself.sd_imageTransition; + } if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) { dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey]; dispatch_group_enter(group); dispatch_main_async_safe(^{ - [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock]; + [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL]; }); // ensure completion block is called after custom setImage process finish dispatch_group_notify(group, dispatch_get_main_queue(), ^{ @@ -156,7 +161,7 @@ static char TAG_ACTIVITY_SHOW; }); } else { dispatch_main_async_safe(^{ - [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock]; + [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL]; callCompletedBlockClojure(); }); } @@ -178,24 +183,79 @@ static char TAG_ACTIVITY_SHOW; } - (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock { + [self sd_setImage:image imageData:imageData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:nil cacheType:0 imageURL:nil]; +} + +- (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) { - setImageBlock(image, imageData); - return; + finalSetImageBlock = setImageBlock; } - #if SD_UIKIT || SD_MAC - if ([self isKindOfClass:[UIImageView class]]) { - UIImageView *imageView = (UIImageView *)self; - imageView.image = image; + else if ([view isKindOfClass:[UIImageView class]]) { + UIImageView *imageView = (UIImageView *)view; + finalSetImageBlock = ^(UIImage *tempImage, NSData *tempData) { + imageView.image = image; + }; + } +#endif +#if SD_UIKIT + else if ([view isKindOfClass:[UIButton class]]) { + UIButton *button = (UIButton *)view; + finalSetImageBlock = ^(UIImage *tempImage, NSData *tempData){ + [button setImage:image forState:UIControlStateNormal]; + }; } #endif + if (transition) { #if SD_UIKIT - if ([self isKindOfClass:[UIButton class]]) { - UIButton *button = (UIButton *)self; - [button setImage:image forState:UIControlStateNormal]; - } + [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); + } + } } - (void)sd_setNeedsLayout { @@ -206,6 +266,15 @@ static char TAG_ACTIVITY_SHOW; #endif } +#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); +} + #pragma mark - Activity indicator #pragma mark - diff --git a/WebImage/SDWebImage.h b/WebImage/SDWebImage.h index df3d176d..e35d1051 100644 --- a/WebImage/SDWebImage.h +++ b/WebImage/SDWebImage.h @@ -34,6 +34,7 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[]; #import #import #import +#import #if SD_MAC || SD_UIKIT #import From fbcfa2808dcb1294026945b4f40878e162ebfe59 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 24 Jan 2018 23:02:45 +0800 Subject: [PATCH 2/9] Use a internal method to avoid thread-safe issue for file manager. Remove that checkIOQueue and add sync version exist API --- SDWebImage/SDImageCache.h | 7 ++++ SDWebImage/SDImageCache.m | 63 ++++++++++++++++++++++----------- SDWebImage/SDImageCacheConfig.h | 8 ++++- SDWebImage/SDImageCacheConfig.m | 1 + 4 files changed, 57 insertions(+), 22 deletions(-) diff --git a/SDWebImage/SDImageCache.h b/SDWebImage/SDImageCache.h index d3c92c17..6003595a 100644 --- a/SDWebImage/SDImageCache.h +++ b/SDWebImage/SDImageCache.h @@ -171,6 +171,13 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot */ - (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock; +/** + * Sync check if image data exists in disk cache already (does not load the image) + * + * @param key the key describing the url + */ +- (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key; + /** * Operation that queries the cache asynchronously and call the completion when done. * diff --git a/SDWebImage/SDImageCache.m b/SDWebImage/SDImageCache.m index 6b926186..162122fb 100644 --- a/SDWebImage/SDImageCache.m +++ b/SDWebImage/SDImageCache.m @@ -106,14 +106,6 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { [[NSNotificationCenter defaultCenter] removeObserver:self]; } -- (void)checkIfQueueIsIOQueue { - const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL); - const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue); - if (strcmp(currentQueueLabel, ioQueueLabel) != 0) { - NSLog(@"This method should be called from the ioQueue"); - } -} - #pragma mark - Cache paths - (void)addReadOnlyCachePath:(nonnull NSString *)path { @@ -201,7 +193,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { } data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format]; } - [self storeImageDataToDisk:data forKey:key]; + [self _storeImageDataToDisk:data forKey:key]; } if (completionBlock) { @@ -221,8 +213,16 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { if (!imageData || !key) { return; } - - [self checkIfQueueIsIOQueue]; + dispatch_sync(self.ioQueue, ^{ + [self _storeImageDataToDisk:imageData forKey:key]; + }); +} + +// Make sure to call form io queue by caller +- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key { + if (!imageData || !key) { + return; + } if (![_fileManager fileExistsAtPath:_diskCachePath]) { [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL]; @@ -233,7 +233,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { // transform to NSUrl NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey]; - [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil]; + [imageData writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil]; // disable iCloud backup if (self.config.shouldDisableiCloud) { @@ -244,15 +244,8 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { #pragma mark - Query and Retrieve Ops - (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock { - dispatch_async(_ioQueue, ^{ - BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]]; - - // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name - // checking the key with and without the extension - if (!exists) { - exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension]; - } - + dispatch_async(self.ioQueue, ^{ + BOOL exists = [self _diskImageDataExistsWithKey:key]; if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(exists); @@ -261,6 +254,34 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { }); } +- (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key { + if (!key) { + return NO; + } + __block BOOL exists = NO; + dispatch_sync(self.ioQueue, ^{ + exists = [self _diskImageDataExistsWithKey:key]; + }); + + return exists; +} + +// Make sure to call form io queue by caller +- (BOOL)_diskImageDataExistsWithKey:(nullable NSString *)key { + if (!key) { + return NO; + } + BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]]; + + // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name + // checking the key with and without the extension + if (!exists) { + exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension]; + } + + return exists; +} + - (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key { return [self.memCache objectForKey:key]; } diff --git a/SDWebImage/SDImageCacheConfig.h b/SDWebImage/SDImageCacheConfig.h index 20f2d108..d3cb5421 100644 --- a/SDWebImage/SDImageCacheConfig.h +++ b/SDWebImage/SDImageCacheConfig.h @@ -29,10 +29,16 @@ /** * The reading options while reading cache from disk. - * Defaults to 0. You can set this to mapped file to improve performance. + * Defaults to 0. You can set this to `NSDataReadingMappedIfSafe` to improve performance. */ @property (assign, nonatomic) NSDataReadingOptions diskCacheReadingOptions; +/** + * The writing options while writing cache to disk. + * Defaults to `NSDataWritingAtomic`. You can set this to `NSDataWritingWithoutOverwriting` to prevent overwriting an existing file. + */ +@property (assign, nonatomic) NSDataWritingOptions diskCacheWritingOptions; + /** * The maximum length of time to keep an image in the cache, in seconds. */ diff --git a/SDWebImage/SDImageCacheConfig.m b/SDWebImage/SDImageCacheConfig.m index 7a5af6cb..923506d0 100644 --- a/SDWebImage/SDImageCacheConfig.m +++ b/SDWebImage/SDImageCacheConfig.m @@ -18,6 +18,7 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week _shouldDisableiCloud = YES; _shouldCacheImagesInMemory = YES; _diskCacheReadingOptions = 0; + _diskCacheWritingOptions = NSDataWritingAtomic; _maxCacheAge = kDefaultCacheMaxCacheAge; _maxCacheSize = 0; } From 9be6ba496e8236cb7cdb064cd63d7634af8eebec Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 24 Jan 2018 23:58:49 +0800 Subject: [PATCH 3/9] A little enhancement to avoid block capture the heap object --- SDWebImage/SDWebImageTransition.h | 2 +- SDWebImage/UIView+WebCache.m | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SDWebImage/SDWebImageTransition.h b/SDWebImage/SDWebImageTransition.h index dff9ef70..06d48d1e 100644 --- a/SDWebImage/SDWebImageTransition.h +++ b/SDWebImage/SDWebImageTransition.h @@ -60,7 +60,7 @@ typedef void (^SDWebImageTransitionCompletionBlock)(BOOL finished); @end -// Convenience way to use transition. Remember to specify the duration +// Convenience way to create transition. Remember to specify the duration // for UIKit, these transition just use the correspond `animationOptions` // for AppKit, these transition use Core Animation in `animations`. So your view must be layer-backed. Set `wantsLayer = YES` before you apply it. diff --git a/SDWebImage/UIView+WebCache.m b/SDWebImage/UIView+WebCache.m index 99e11b2a..e9f6dc68 100644 --- a/SDWebImage/UIView+WebCache.m +++ b/SDWebImage/UIView+WebCache.m @@ -195,16 +195,16 @@ static char TAG_ACTIVITY_SHOW; #if SD_UIKIT || SD_MAC else if ([view isKindOfClass:[UIImageView class]]) { UIImageView *imageView = (UIImageView *)view; - finalSetImageBlock = ^(UIImage *tempImage, NSData *tempData) { - imageView.image = image; + finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData) { + imageView.image = setImage; }; } #endif #if SD_UIKIT else if ([view isKindOfClass:[UIButton class]]) { UIButton *button = (UIButton *)view; - finalSetImageBlock = ^(UIImage *tempImage, NSData *tempData){ - [button setImage:image forState:UIControlStateNormal]; + finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData){ + [button setImage:setImage forState:UIControlStateNormal]; }; } #endif From 8590388a6ecdf8b9927b5af4731a0f3c36e6f51f Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 19 Jan 2018 15:46:05 +0800 Subject: [PATCH 4/9] Add WebCache category for NSButton on macOS --- SDWebImage.xcodeproj/project.pbxproj | 8 + SDWebImage/NSButton+WebCache.h | 237 +++++++++++++++++++++++++++ SDWebImage/NSButton+WebCache.m | 102 ++++++++++++ WebImage/SDWebImage.h | 1 + 4 files changed, 348 insertions(+) create mode 100644 SDWebImage/NSButton+WebCache.h create mode 100644 SDWebImage/NSButton+WebCache.m diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index 270e37b9..b503d080 100644 --- a/SDWebImage.xcodeproj/project.pbxproj +++ b/SDWebImage.xcodeproj/project.pbxproj @@ -37,6 +37,8 @@ 00733A711BC4880E00A5A117 /* UIImageView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 53922D95148C56230056699D /* UIImageView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 00733A721BC4880E00A5A117 /* UIView+WebCacheOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = AB615301192DA24600A2D8E9 /* UIView+WebCacheOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 00733A731BC4880E00A5A117 /* SDWebImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A2CAE031AB4BB5400B6BC39 /* SDWebImage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321DB3612011D4D70015D2CB /* NSButton+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 321DB35F2011D4D60015D2CB /* NSButton+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321DB3622011D4D70015D2CB /* NSButton+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 321DB3602011D4D60015D2CB /* NSButton+WebCache.m */; }; 321E60861F38E8C800405457 /* SDWebImageCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 321E60841F38E8C800405457 /* SDWebImageCoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 321E60871F38E8C800405457 /* SDWebImageCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 321E60841F38E8C800405457 /* SDWebImageCoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 321E60881F38E8C800405457 /* SDWebImageCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 321E60841F38E8C800405457 /* SDWebImageCoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1285,6 +1287,8 @@ /* Begin PBXFileReference section */ 00733A4C1BC487C000A5A117 /* SDWebImage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SDWebImage.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 321DB35F2011D4D60015D2CB /* NSButton+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSButton+WebCache.h"; path = "SDWebImage/NSButton+WebCache.h"; sourceTree = ""; }; + 321DB3602011D4D60015D2CB /* NSButton+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSButton+WebCache.m"; path = "SDWebImage/NSButton+WebCache.m"; sourceTree = ""; }; 321E60841F38E8C800405457 /* SDWebImageCoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageCoder.h; sourceTree = ""; }; 321E60851F38E8C800405457 /* SDWebImageCoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageCoder.m; sourceTree = ""; }; 321E60921F38E8ED00405457 /* SDWebImageImageIOCoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageImageIOCoder.h; sourceTree = ""; }; @@ -1640,6 +1644,8 @@ children = ( 4397D2F41D0DE2DF00BB2784 /* NSImage+WebCache.h */, 4397D2F51D0DE2DF00BB2784 /* NSImage+WebCache.m */, + 321DB35F2011D4D60015D2CB /* NSButton+WebCache.h */, + 321DB3602011D4D60015D2CB /* NSButton+WebCache.m */, 535699B415113E7300A4C397 /* MKAnnotationView+WebCache.h */, 535699B515113E7300A4C397 /* MKAnnotationView+WebCache.m */, 53922D93148C56230056699D /* UIButton+WebCache.h */, @@ -2295,6 +2301,7 @@ 321E60B51F38E90100405457 /* SDWebImageWebPCoder.h in Headers */, 4397D2EB1D0DDD8C00BB2784 /* NSData+ImageContentType.h in Headers */, 80377C851F2F666400F89830 /* huffman_encode_utils.h in Headers */, + 321DB3612011D4D70015D2CB /* NSButton+WebCache.h in Headers */, 807A122D1F89636300EC2A9B /* SDWebImageCodersManager.h in Headers */, 4397D2ED1D0DDD8C00BB2784 /* mux_types.h in Headers */, 80377C831F2F666400F89830 /* filters_utils.h in Headers */, @@ -3198,6 +3205,7 @@ 80377C8C1F2F666400F89830 /* random_utils.c in Sources */, 323F8BAD1F38EF770092B609 /* picture_psnr_enc.c in Sources */, 323F8BC51F38EF770092B609 /* quant_enc.c in Sources */, + 321DB3622011D4D70015D2CB /* NSButton+WebCache.m in Sources */, 80377C7F1F2F666400F89830 /* color_cache_utils.c in Sources */, 80377E331F2F66A800F89830 /* alpha_processing_neon.c in Sources */, 80377E401F2F66A800F89830 /* dec_clip_tables.c in Sources */, diff --git a/SDWebImage/NSButton+WebCache.h b/SDWebImage/NSButton+WebCache.h new file mode 100644 index 00000000..56b45a56 --- /dev/null +++ b/SDWebImage/NSButton+WebCache.h @@ -0,0 +1,237 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDWebImageCompat.h" + +#if SD_MAC + +#import "SDWebImageManager.h" + +@interface NSButton (WebCache) + +#pragma mark - Image + +/** + * Set the button `image` with an `url`. + * + * The download is asynchronous and cached. + * + * @param url The url for the image. + */ +- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT; + +/** + * Set the button `image` with an `url` and a placeholder. + * + * The download is asynchronous and cached. + * + * @param url The url for the image. + * @param placeholder The image to be set initially, until the image request finishes. + * @see sd_setImageWithURL:placeholderImage:options: + */ +- (void)sd_setImageWithURL:(nullable NSURL *)url + placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT; + +/** + * Set the button `image` with an `url`, placeholder and custom options. + * + * The download is asynchronous and cached. + * + * @param url The url for the image. + * @param placeholder The image to be set initially, until the image request finishes. + * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. + */ +- (void)sd_setImageWithURL:(nullable NSURL *)url + placeholderImage:(nullable UIImage *)placeholder + options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT; + +/** + * Set the button `image` with an `url`. + * + * The download is asynchronous and cached. + * + * @param url The url for the image. + * @param completedBlock A block called when operation has been completed. This block has no return value + * and takes the requested UIImage as first parameter. In case of error the image parameter + * is nil and the second parameter may contain an NSError. The third parameter is a Boolean + * indicating if the image was retrieved from the local cache or from the network. + * The fourth parameter is the original image url. + */ +- (void)sd_setImageWithURL:(nullable NSURL *)url + completed:(nullable SDExternalCompletionBlock)completedBlock; + +/** + * Set the button `image` with an `url`, placeholder. + * + * The download is asynchronous and cached. + * + * @param url The url for the image. + * @param placeholder The image to be set initially, until the image request finishes. + * @param completedBlock A block called when operation has been completed. This block has no return value + * and takes the requested UIImage as first parameter. In case of error the image parameter + * is nil and the second parameter may contain an NSError. The third parameter is a Boolean + * indicating if the image was retrieved from the local cache or from the network. + * The fourth parameter is the original image url. + */ +- (void)sd_setImageWithURL:(nullable NSURL *)url + placeholderImage:(nullable UIImage *)placeholder + completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT; + +/** + * Set the button `image` with an `url`, placeholder and custom options. + * + * The download is asynchronous and cached. + * + * @param url The url for the image. + * @param placeholder The image to be set initially, until the image request finishes. + * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. + * @param completedBlock A block called when operation has been completed. This block has no return value + * and takes the requested UIImage as first parameter. In case of error the image parameter + * is nil and the second parameter may contain an NSError. The third parameter is a Boolean + * indicating if the image was retrieved from the local cache or from the network. + * The fourth parameter is the original image url. + */ +- (void)sd_setImageWithURL:(nullable NSURL *)url + placeholderImage:(nullable UIImage *)placeholder + options:(SDWebImageOptions)options + completed:(nullable SDExternalCompletionBlock)completedBlock; + +/** + * Set the button `image` with an `url`, placeholder and custom options. + * + * The download is asynchronous and cached. + * + * @param url The url for the image. + * @param placeholder The image to be set initially, until the image request finishes. + * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. + * @param progressBlock A block called while image is downloading + * @note the progress block is executed on a background queue + * @param completedBlock A block called when operation has been completed. This block has no return value + * and takes the requested UIImage as first parameter. In case of error the image parameter + * is nil and the second parameter may contain an NSError. The third parameter is a Boolean + * indicating if the image was retrieved from the local cache or from the network. + * The fourth parameter is the original image url. + */ +- (void)sd_setImageWithURL:(nullable NSURL *)url + placeholderImage:(nullable UIImage *)placeholder + options:(SDWebImageOptions)options + progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock + completed:(nullable SDExternalCompletionBlock)completedBlock; + +#pragma mark - Alternate Image + +/** + * Set the button `alternateImage` with an `url`. + * + * The download is asynchronous and cached. + * + * @param url The url for the alternateImage. + */ +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT; + +/** + * Set the button `alternateImage` with an `url` and a placeholder. + * + * The download is asynchronous and cached. + * + * @param url The url for the alternateImage. + * @param placeholder The alternateImage to be set initially, until the alternateImage request finishes. + * @see sd_setAlternateImageWithURL:placeholderImage:options: + */ +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url + placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT; + +/** + * Set the button `alternateImage` with an `url`, placeholder and custom options. + * + * The download is asynchronous and cached. + * + * @param url The url for the alternateImage. + * @param placeholder The alternateImage to be set initially, until the alternateImage request finishes. + * @param options The options to use when downloading the alternateImage. @see SDWebImageOptions for the possible values. + */ +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url + placeholderImage:(nullable UIImage *)placeholder + options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT; + +/** + * Set the button `alternateImage` with an `url`. + * + * The download is asynchronous and cached. + * + * @param url The url for the alternateImage. + * @param completedBlock A block called when operation has been completed. This block has no return value + * and takes the requested UIImage as first parameter. In case of error the alternateImage parameter + * is nil and the second parameter may contain an NSError. The third parameter is a Boolean + * indicating if the alternateImage was retrieved from the local cache or from the network. + * The fourth parameter is the original alternateImage url. + */ +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url + completed:(nullable SDExternalCompletionBlock)completedBlock; + +/** + * Set the button `alternateImage` with an `url`, placeholder. + * + * The download is asynchronous and cached. + * + * @param url The url for the alternateImage. + * @param placeholder The alternateImage to be set initially, until the alternateImage request finishes. + * @param completedBlock A block called when operation has been completed. This block has no return value + * and takes the requested UIImage as first parameter. In case of error the alternateImage parameter + * is nil and the second parameter may contain an NSError. The third parameter is a Boolean + * indicating if the alternateImage was retrieved from the local cache or from the network. + * The fourth parameter is the original alternateImage url. + */ +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url + placeholderImage:(nullable UIImage *)placeholder + completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT; + +/** + * Set the button `alternateImage` with an `url`, placeholder and custom options. + * + * The download is asynchronous and cached. + * + * @param url The url for the alternateImage. + * @param placeholder The alternateImage to be set initially, until the alternateImage request finishes. + * @param options The options to use when downloading the alternateImage. @see SDWebImageOptions for the possible values. + * @param completedBlock A block called when operation has been completed. This block has no return value + * and takes the requested UIImage as first parameter. In case of error the alternateImage parameter + * is nil and the second parameter may contain an NSError. The third parameter is a Boolean + * indicating if the alternateImage was retrieved from the local cache or from the network. + * The fourth parameter is the original alternateImage url. + */ +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url + placeholderImage:(nullable UIImage *)placeholder + options:(SDWebImageOptions)options + completed:(nullable SDExternalCompletionBlock)completedBlock; + +/** + * Set the button `alternateImage` with an `url`, placeholder and custom options. + * + * The download is asynchronous and cached. + * + * @param url The url for the alternateImage. + * @param placeholder The alternateImage to be set initially, until the alternateImage request finishes. + * @param options The options to use when downloading the alternateImage. @see SDWebImageOptions for the possible values. + * @param progressBlock A block called while alternateImage is downloading + * @note the progress block is executed on a background queue + * @param completedBlock A block called when operation has been completed. This block has no return value + * and takes the requested UIImage as first parameter. In case of error the alternateImage parameter + * is nil and the second parameter may contain an NSError. The third parameter is a Boolean + * indicating if the alternateImage was retrieved from the local cache or from the network. + * The fourth parameter is the original alternateImage url. + */ +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url + placeholderImage:(nullable UIImage *)placeholder + options:(SDWebImageOptions)options + progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock + completed:(nullable SDExternalCompletionBlock)completedBlock; + +@end + +#endif diff --git a/SDWebImage/NSButton+WebCache.m b/SDWebImage/NSButton+WebCache.m new file mode 100644 index 00000000..ead14f35 --- /dev/null +++ b/SDWebImage/NSButton+WebCache.m @@ -0,0 +1,102 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "NSButton+WebCache.h" + +#if SD_MAC + +#import "UIView+WebCache.h" + +@implementation NSButton (WebCache) + +#pragma mark - Image + +- (void)sd_setImageWithURL:(nullable NSURL *)url { + [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil]; +} + +- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder { + [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil]; +} + +- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options { + [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil]; +} + +- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock { + [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock]; +} + +- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock { + [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock]; +} + +- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock { + [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock]; +} + +- (void)sd_setImageWithURL:(nullable NSURL *)url + placeholderImage:(nullable UIImage *)placeholder + options:(SDWebImageOptions)options + progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock + completed:(nullable SDExternalCompletionBlock)completedBlock { + [self sd_internalSetImageWithURL:url + placeholderImage:placeholder + options:options + operationKey:nil + setImageBlock:nil + progress:progressBlock + completed:completedBlock]; +} + +#pragma mark - Alternate Image + +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url { + [self sd_setAlternateImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil]; +} + +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder { + [self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil]; +} + +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options { + [self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil]; +} + +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock { + [self sd_setAlternateImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock]; +} + +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock { + [self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock]; +} + +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock { + [self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock]; +} + +- (void)sd_setAlternateImageWithURL:(nullable NSURL *)url + placeholderImage:(nullable UIImage *)placeholder + options:(SDWebImageOptions)options + progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock + completed:(nullable SDExternalCompletionBlock)completedBlock { + __weak typeof(self)weakSelf = self; + [self sd_internalSetImageWithURL:url + placeholderImage:placeholder + options:options + operationKey:nil + setImageBlock:^(NSImage * _Nullable image, NSData * _Nullable imageData) { + weakSelf.alternateImage = image; + } + progress:progressBlock + completed:completedBlock]; +} + +@end + +#endif diff --git a/WebImage/SDWebImage.h b/WebImage/SDWebImage.h index e35d1051..5e02f993 100644 --- a/WebImage/SDWebImage.h +++ b/WebImage/SDWebImage.h @@ -54,6 +54,7 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[]; #if SD_MAC #import + #import #endif #if SD_UIKIT From 634e4f4522554071946690d542dc7a561ced2568 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 19 Jan 2018 18:06:20 +0800 Subject: [PATCH 5/9] Fix that reset alternateImage cancel the image load operation for NSButton+WebCache --- SDWebImage/NSButton+WebCache.h | 12 ++++++++++++ SDWebImage/NSButton+WebCache.m | 20 +++++++++++++++++--- SDWebImage/UIButton+WebCache.h | 2 +- SDWebImage/UIButton+WebCache.m | 4 +++- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/SDWebImage/NSButton+WebCache.h b/SDWebImage/NSButton+WebCache.h index 56b45a56..42c3fc69 100644 --- a/SDWebImage/NSButton+WebCache.h +++ b/SDWebImage/NSButton+WebCache.h @@ -232,6 +232,18 @@ progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; +#pragma mark - Cancel + +/** + * Cancel the current image download + */ +- (void)sd_cancelCurrentImageLoad; + +/** + * Cancel the current alternateImage download + */ +- (void)sd_cancelCurrentAlternateImageLoad; + @end #endif diff --git a/SDWebImage/NSButton+WebCache.m b/SDWebImage/NSButton+WebCache.m index ead14f35..5a012c36 100644 --- a/SDWebImage/NSButton+WebCache.m +++ b/SDWebImage/NSButton+WebCache.m @@ -10,6 +10,7 @@ #if SD_MAC +#import "UIView+WebCacheOperation.h" #import "UIView+WebCache.h" @implementation NSButton (WebCache) @@ -45,11 +46,14 @@ options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { + __weak typeof(self)weakSelf = self; [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options - operationKey:nil - setImageBlock:nil + operationKey:@"NSButtonImageOperation" + setImageBlock:^(NSImage * _Nullable image, NSData * _Nullable imageData) { + weakSelf.image = image; + } progress:progressBlock completed:completedBlock]; } @@ -89,7 +93,7 @@ [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options - operationKey:nil + operationKey:@"NSButtonAlternateImageOperation" setImageBlock:^(NSImage * _Nullable image, NSData * _Nullable imageData) { weakSelf.alternateImage = image; } @@ -97,6 +101,16 @@ completed:completedBlock]; } +#pragma mark - Cancel + +- (void)sd_cancelCurrentImageLoad { + [self sd_cancelImageLoadOperationWithKey:@"NSButtonImageOperation"]; +} + +- (void)sd_cancelCurrentAlternateImageLoad { + [self sd_cancelImageLoadOperationWithKey:@"NSButtonAlternateImageOperation"]; +} + @end #endif diff --git a/SDWebImage/UIButton+WebCache.h b/SDWebImage/UIButton+WebCache.h index b6c4a2a2..61fada62 100644 --- a/SDWebImage/UIButton+WebCache.h +++ b/SDWebImage/UIButton+WebCache.h @@ -128,7 +128,7 @@ options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock; -#pragma mark - Background image +#pragma mark - Background Image /** * Get the current background image URL. diff --git a/SDWebImage/UIButton+WebCache.m b/SDWebImage/UIButton+WebCache.m index 11e34b18..83aebd04 100644 --- a/SDWebImage/UIButton+WebCache.m +++ b/SDWebImage/UIButton+WebCache.m @@ -87,7 +87,7 @@ static inline NSString * backgroundImageURLKeyForState(UIControlState state) { completed:completedBlock]; } -#pragma mark - Background image +#pragma mark - Background Image - (nullable NSURL *)sd_currentBackgroundImageURL { NSURL *url = self.imageURLStorage[backgroundImageURLKeyForState(self.state)]; @@ -146,6 +146,8 @@ static inline NSString * backgroundImageURLKeyForState(UIControlState state) { completed:completedBlock]; } +#pragma mark - Cancel + - (void)sd_cancelImageLoadForState:(UIControlState)state { [self sd_cancelImageLoadOperationWithKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)]]; } From 7996b0dac86b31da8458cb654fbaa1b649392e61 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 19 Jan 2018 18:45:02 +0800 Subject: [PATCH 6/9] Update macOS demo to add a clear cache button using NSButton category --- .../Assets.xcassets/Contents.json | 6 +++++ .../Base.lproj/Main.storyboard | 23 ++++++++++++++++--- Examples/SDWebImage OSX Demo/ViewController.m | 15 ++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 Examples/SDWebImage OSX Demo/Assets.xcassets/Contents.json diff --git a/Examples/SDWebImage OSX Demo/Assets.xcassets/Contents.json b/Examples/SDWebImage OSX Demo/Assets.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Examples/SDWebImage OSX Demo/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/SDWebImage OSX Demo/Base.lproj/Main.storyboard b/Examples/SDWebImage OSX Demo/Base.lproj/Main.storyboard index 29684e48..eaa4ae59 100644 --- a/Examples/SDWebImage OSX Demo/Base.lproj/Main.storyboard +++ b/Examples/SDWebImage OSX Demo/Base.lproj/Main.storyboard @@ -1,8 +1,9 @@ - - + + - + + @@ -656,6 +657,9 @@ + + + @@ -675,23 +679,36 @@ + + + + + + diff --git a/Examples/SDWebImage OSX Demo/ViewController.m b/Examples/SDWebImage OSX Demo/ViewController.m index 6a8ec69f..ad534b77 100644 --- a/Examples/SDWebImage OSX Demo/ViewController.m +++ b/Examples/SDWebImage OSX Demo/ViewController.m @@ -16,6 +16,7 @@ @property (weak) IBOutlet NSImageView *imageView2; @property (weak) IBOutlet NSImageView *imageView3; @property (weak) IBOutlet NSImageView *imageView4; +@property (weak) IBOutlet NSButton *clearCacheButton; @end @@ -36,6 +37,20 @@ self.imageView4.wantsLayer = YES; self.imageView4.sd_imageTransition = SDWebImageTransition.fadeTransition; [self.imageView4 sd_setImageWithURL:[NSURL URLWithString:@"http://s3.amazonaws.com/fast-image-cache/demo-images/FICDDemoImage001.jpg"] placeholderImage:nil options:SDWebImageForceTransition]; + + self.clearCacheButton.target = self; + self.clearCacheButton.action = @selector(clearCacheButtonClicked:); + [self.clearCacheButton sd_setImageWithURL:[NSURL URLWithString:@"https://png.icons8.com/color/100/000000/delete-sign.png"]]; + [self.clearCacheButton sd_setAlternateImageWithURL:[NSURL URLWithString:@"https://png.icons8.com/color/100/000000/checkmark.png"]]; +} + +- (void)clearCacheButtonClicked:(NSResponder *)sender { + NSButton *button = (NSButton *)sender; + button.state = NSControlStateValueOn; + [[SDImageCache sharedImageCache] clearMemory]; + [[SDImageCache sharedImageCache] clearDiskOnCompletion:^{ + button.state = NSControlStateValueOff; + }]; } - (void)setRepresentedObject:(id)representedObject { From bccdd2a76609058a31ab11e91579d56e9675922d Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 26 Jan 2018 14:28:50 +0800 Subject: [PATCH 7/9] Add the current image/alternateImage url for NSButton category. A little code refactoring --- SDWebImage/NSButton+WebCache.h | 10 +++++++++ SDWebImage/NSButton+WebCache.m | 39 +++++++++++++++++++++++++++++---- SDWebImage/UIButton+WebCache.m | 40 +++++++++++++++++++++------------- 3 files changed, 70 insertions(+), 19 deletions(-) diff --git a/SDWebImage/NSButton+WebCache.h b/SDWebImage/NSButton+WebCache.h index 42c3fc69..57f7115e 100644 --- a/SDWebImage/NSButton+WebCache.h +++ b/SDWebImage/NSButton+WebCache.h @@ -16,6 +16,11 @@ #pragma mark - Image +/** + * Get the current image URL. + */ +- (nullable NSURL *)sd_currentImageURL; + /** * Set the button `image` with an `url`. * @@ -125,6 +130,11 @@ #pragma mark - Alternate Image +/** + * Get the current alternateImage URL. + */ +- (nullable NSURL *)sd_currentAlternateImageURL; + /** * Set the button `alternateImage` with an `url`. * diff --git a/SDWebImage/NSButton+WebCache.m b/SDWebImage/NSButton+WebCache.m index 5a012c36..3ca080f2 100644 --- a/SDWebImage/NSButton+WebCache.m +++ b/SDWebImage/NSButton+WebCache.m @@ -10,9 +10,18 @@ #if SD_MAC +#import "objc/runtime.h" #import "UIView+WebCacheOperation.h" #import "UIView+WebCache.h" +static inline NSString * imageOperationKey() { + return @"NSButtonImageOperation"; +} + +static inline NSString * alternateImageOperationKey() { + return @"NSButtonAlternateImageOperation"; +} + @implementation NSButton (WebCache) #pragma mark - Image @@ -46,11 +55,13 @@ options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { + self.sd_currentImageURL = url; + __weak typeof(self)weakSelf = self; [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options - operationKey:@"NSButtonImageOperation" + operationKey:imageOperationKey() setImageBlock:^(NSImage * _Nullable image, NSData * _Nullable imageData) { weakSelf.image = image; } @@ -89,11 +100,13 @@ options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { + self.sd_currentAlternateImageURL = url; + __weak typeof(self)weakSelf = self; [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options - operationKey:@"NSButtonAlternateImageOperation" + operationKey:alternateImageOperationKey() setImageBlock:^(NSImage * _Nullable image, NSData * _Nullable imageData) { weakSelf.alternateImage = image; } @@ -104,11 +117,29 @@ #pragma mark - Cancel - (void)sd_cancelCurrentImageLoad { - [self sd_cancelImageLoadOperationWithKey:@"NSButtonImageOperation"]; + [self sd_cancelImageLoadOperationWithKey:imageOperationKey()]; } - (void)sd_cancelCurrentAlternateImageLoad { - [self sd_cancelImageLoadOperationWithKey:@"NSButtonAlternateImageOperation"]; + [self sd_cancelImageLoadOperationWithKey:alternateImageOperationKey()]; +} + +#pragma mar - Private + +- (NSURL *)sd_currentImageURL { + return objc_getAssociatedObject(self, @selector(sd_currentImageURL)); +} + +- (void)setSd_currentImageURL:(NSURL *)sd_currentImageURL { + objc_setAssociatedObject(self, @selector(sd_currentImageURL), sd_currentImageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSURL *)sd_currentAlternateImageURL { + return objc_getAssociatedObject(self, @selector(sd_currentAlternateImageURL)); +} + +- (void)setSd_currentAlternateImageURL:(NSURL *)sd_currentAlternateImageURL { + objc_setAssociatedObject(self, @selector(sd_currentAlternateImageURL), sd_currentAlternateImageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end diff --git a/SDWebImage/UIButton+WebCache.m b/SDWebImage/UIButton+WebCache.m index 83aebd04..8cdadb67 100644 --- a/SDWebImage/UIButton+WebCache.m +++ b/SDWebImage/UIButton+WebCache.m @@ -26,22 +26,30 @@ static inline NSString * backgroundImageURLKeyForState(UIControlState state) { return [NSString stringWithFormat:@"backgroundImage_%lu", (unsigned long)state]; } +static inline NSString * imageOperationKeyForState(UIControlState state) { + return [NSString stringWithFormat:@"UIButtonImageOperation%lu", (unsigned long)state]; +} + +static inline NSString * backgroundImageOperationKeyForState(UIControlState state) { + return [NSString stringWithFormat:@"UIButtonBackgroundImageOperation%lu", (unsigned long)state]; +} + @implementation UIButton (WebCache) #pragma mark - Image - (nullable NSURL *)sd_currentImageURL { - NSURL *url = self.imageURLStorage[imageURLKeyForState(self.state)]; + NSURL *url = self.sd_imageURLStorage[imageURLKeyForState(self.state)]; if (!url) { - url = self.imageURLStorage[imageURLKeyForState(UIControlStateNormal)]; + url = self.sd_imageURLStorage[imageURLKeyForState(UIControlStateNormal)]; } return url; } - (nullable NSURL *)sd_imageURLForState:(UIControlState)state { - return self.imageURLStorage[imageURLKeyForState(state)]; + return self.sd_imageURLStorage[imageURLKeyForState(state)]; } - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state { @@ -70,16 +78,16 @@ static inline NSString * backgroundImageURLKeyForState(UIControlState state) { options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock { if (!url) { - [self.imageURLStorage removeObjectForKey:imageURLKeyForState(state)]; + [self.sd_imageURLStorage removeObjectForKey:imageURLKeyForState(state)]; } else { - self.imageURLStorage[imageURLKeyForState(state)] = url; + self.sd_imageURLStorage[imageURLKeyForState(state)] = url; } __weak typeof(self)weakSelf = self; [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options - operationKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)] + operationKey:imageOperationKeyForState(state) setImageBlock:^(UIImage *image, NSData *imageData) { [weakSelf setImage:image forState:state]; } @@ -90,17 +98,17 @@ static inline NSString * backgroundImageURLKeyForState(UIControlState state) { #pragma mark - Background Image - (nullable NSURL *)sd_currentBackgroundImageURL { - NSURL *url = self.imageURLStorage[backgroundImageURLKeyForState(self.state)]; + NSURL *url = self.sd_imageURLStorage[backgroundImageURLKeyForState(self.state)]; if (!url) { - url = self.imageURLStorage[backgroundImageURLKeyForState(UIControlStateNormal)]; + url = self.sd_imageURLStorage[backgroundImageURLKeyForState(UIControlStateNormal)]; } return url; } - (nullable NSURL *)sd_backgroundImageURLForState:(UIControlState)state { - return self.imageURLStorage[backgroundImageURLKeyForState(state)]; + return self.sd_imageURLStorage[backgroundImageURLKeyForState(state)]; } - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state { @@ -129,16 +137,16 @@ static inline NSString * backgroundImageURLKeyForState(UIControlState state) { options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock { if (!url) { - [self.imageURLStorage removeObjectForKey:backgroundImageURLKeyForState(state)]; + [self.sd_imageURLStorage removeObjectForKey:backgroundImageURLKeyForState(state)]; } else { - self.imageURLStorage[backgroundImageURLKeyForState(state)] = url; + self.sd_imageURLStorage[backgroundImageURLKeyForState(state)] = url; } __weak typeof(self)weakSelf = self; [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options - operationKey:[NSString stringWithFormat:@"UIButtonBackgroundImageOperation%@", @(state)] + operationKey:backgroundImageOperationKeyForState(state) setImageBlock:^(UIImage *image, NSData *imageData) { [weakSelf setBackgroundImage:image forState:state]; } @@ -149,14 +157,16 @@ static inline NSString * backgroundImageURLKeyForState(UIControlState state) { #pragma mark - Cancel - (void)sd_cancelImageLoadForState:(UIControlState)state { - [self sd_cancelImageLoadOperationWithKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)]]; + [self sd_cancelImageLoadOperationWithKey:imageOperationKeyForState(state)]; } - (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state { - [self sd_cancelImageLoadOperationWithKey:[NSString stringWithFormat:@"UIButtonBackgroundImageOperation%@", @(state)]]; + [self sd_cancelImageLoadOperationWithKey:backgroundImageOperationKeyForState(state)]; } -- (SDStateImageURLDictionary *)imageURLStorage { +#pragma mark - Private + +- (SDStateImageURLDictionary *)sd_imageURLStorage { SDStateImageURLDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey); if (!storage) { storage = [NSMutableDictionary dictionary]; From 311c9e1a5ef95b6b51ca0467962f4f4cf059ecbf Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 26 Jan 2018 19:15:26 +0800 Subject: [PATCH 8/9] Use a dispatch semaphore to keep thread safe for downloader because it need the hold cancel and add procedure be thread-safe --- SDWebImage/SDWebImageDownloader.m | 61 ++++++++++++------------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/SDWebImage/SDWebImageDownloader.m b/SDWebImage/SDWebImageDownloader.m index 12c66748..88d22bf5 100644 --- a/SDWebImage/SDWebImageDownloader.m +++ b/SDWebImage/SDWebImageDownloader.m @@ -9,6 +9,9 @@ #import "SDWebImageDownloader.h" #import "SDWebImageDownloaderOperation.h" +#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); +#define UNLOCK(lock) dispatch_semaphore_signal(lock); + @interface SDWebImageDownloadToken () @property (nonatomic, weak, nullable) NSOperation *downloadOperation; @@ -36,6 +39,7 @@ @property (assign, nonatomic, nullable) Class operationClass; @property (strong, nonatomic, nonnull) NSMutableDictionary *URLOperations; @property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders; +@property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock; // a lock to keep the access to `URLOperations` thread-safe // The session in which data tasks will run @property (strong, nonatomic) NSURLSession *session; @@ -94,6 +98,7 @@ #else _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy]; #endif + _operationsLock = dispatch_semaphore_create(1); _downloadTimeout = 15.0; [self createNewSessionWithConfiguration:sessionConfiguration]; @@ -232,11 +237,15 @@ if (!url) { return; } - SDWebImageDownloaderOperation *operation = [self operationForURL:url]; - BOOL canceled = [operation cancel:token.downloadOperationCancelToken]; - if (canceled) { - [self removeOperationForURL:url]; + LOCK(self.operationsLock); + SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url]; + if (operation) { + BOOL canceled = [operation cancel:token.downloadOperationCancelToken]; + if (canceled) { + [self.URLOperations removeObjectForKey:url]; + } } + UNLOCK(self.operationsLock); } - (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock @@ -251,17 +260,24 @@ return nil; } - SDWebImageDownloaderOperation *operation = [self operationForURL:url]; + LOCK(self.operationsLock); + SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url]; if (!operation) { operation = createCallback(); - [self setOperation:operation forURL:url]; - __weak typeof(self) wself = self; operation.completionBlock = ^{ __strong typeof(wself) sself = wself; - [sself removeOperationForURL:url]; + if (!sself) { + return; + } + LOCK(sself.operationsLock); + [sself.URLOperations removeObjectForKey:url]; + UNLOCK(sself.operationsLock); }; + [self.URLOperations setObject:operation forKey:url]; } + UNLOCK(self.operationsLock); + id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock]; SDWebImageDownloadToken *token = [SDWebImageDownloadToken new]; @@ -282,35 +298,6 @@ #pragma mark Helper methods -- (SDWebImageDownloaderOperation *)operationForURL:(NSURL *)url { - if (!url) { - return nil; - } - SDWebImageDownloaderOperation *operation; - @synchronized (self.URLOperations) { - operation = [self.URLOperations objectForKey:url]; - } - return operation; -} - -- (void)setOperation:(SDWebImageDownloaderOperation *)operation forURL:(NSURL *)url { - if (!operation || !url) { - return; - } - @synchronized (self.URLOperations) { - [self.URLOperations setObject:operation forKey:url]; - } -} - -- (void)removeOperationForURL:(NSURL *)url { - if (!url) { - return; - } - @synchronized (self.URLOperations) { - [self.URLOperations removeObjectForKey:url]; - } -} - - (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task { SDWebImageDownloaderOperation *returnOperation = nil; for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations) { From 5308754e172ba7bf523484035776ab2a92315b82 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 26 Jan 2018 21:27:07 +0800 Subject: [PATCH 9/9] Update the comments and a little enhancement for FLAnimatedImageView Category --- .../FLAnimatedImageView+WebCache.m | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/SDWebImage/FLAnimatedImage/FLAnimatedImageView+WebCache.m b/SDWebImage/FLAnimatedImage/FLAnimatedImageView+WebCache.m index 84df3e1f..81cb7d49 100644 --- a/SDWebImage/FLAnimatedImage/FLAnimatedImageView+WebCache.m +++ b/SDWebImage/FLAnimatedImage/FLAnimatedImageView+WebCache.m @@ -72,23 +72,27 @@ operationKey:nil setImageBlock:^(UIImage *image, NSData *imageData) { SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData]; - __block FLAnimatedImage *animatedImage = image.sd_FLAnimatedImage; // We could not directlly create the animated image on bacakground queue because it's time consuming, by the time we set it back, the current runloop has passed and the placeholder has been rendered and then replaced with animated image, this cause a flashing. - // Previously we use a trick to firstly set the static poster image, then set animated image back to avoid flashing, but this trick fail when using with UIView transition because it's based on the Core Animation. Core Animation will capture the current layer state to do rendering, so even we later set it back, the transition will not update + // Previously we use a trick to firstly set the static poster image, then set animated image back to avoid flashing, but this trick fail when using with UIView transition because it's based on the Core Animation. Core Animation will capture the current layer state to do rendering, so even we later set it back, the transition will not update. (it's recommended to use `SDWebImageTransition` instead) // So we have no choice to force store the FLAnimatedImage into memory cache using a associated object binding to UIImage instance. This consumed memory is adoptable and much smaller than `_UIAnimatedImage` for big GIF - if (animatedImage || imageFormat == SDImageFormatGIF) { - if (animatedImage) { - weakSelf.animatedImage = animatedImage; + FLAnimatedImage *associatedAnimatedImage = image.sd_FLAnimatedImage; + if (associatedAnimatedImage || imageFormat == SDImageFormatGIF) { + if (associatedAnimatedImage) { + weakSelf.animatedImage = associatedAnimatedImage; weakSelf.image = nil; if (group) { dispatch_group_leave(group); } } else { + // Firstly set the static poster image to avoid flashing + UIImage *posterImage = image.images ? image.images.firstObject : image; + weakSelf.image = posterImage; + weakSelf.animatedImage = nil; // The imageData should not be nil, create FLAnimatedImage in global queue because it's time consuming, then set it back dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ - animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData]; - image.sd_FLAnimatedImage = animatedImage; + FLAnimatedImage *animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData]; dispatch_async(dispatch_get_main_queue(), ^{ + image.sd_FLAnimatedImage = animatedImage; weakSelf.animatedImage = animatedImage; weakSelf.image = nil; if (group) {