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/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 2d7e8774..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 @@ -33,7 +34,23 @@ [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]; + + 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 { diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index a6e7779e..33182a00 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, ); }; }; @@ -324,6 +326,18 @@ 324DF4BD200A14DC008A84CC /* SDWebImageDefine.m in Sources */ = {isa = PBXBuildFile; fileRef = 324DF4B3200A14DC008A84CC /* SDWebImageDefine.m */; }; 324DF4BE200A14DC008A84CC /* SDWebImageDefine.m in Sources */ = {isa = PBXBuildFile; fileRef = 324DF4B3200A14DC008A84CC /* SDWebImageDefine.m */; }; 324DF4BF200A14DC008A84CC /* SDWebImageDefine.m in Sources */ = {isa = PBXBuildFile; fileRef = 324DF4B3200A14DC008A84CC /* SDWebImageDefine.m */; }; + 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, ); }; }; @@ -1302,6 +1316,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 = ""; }; @@ -1349,6 +1365,8 @@ 323F8B3D1F38EF770092B609 /* muxread.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = muxread.c; sourceTree = ""; }; 324DF4B2200A14DC008A84CC /* SDWebImageDefine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageDefine.h; sourceTree = ""; }; 324DF4B3200A14DC008A84CC /* SDWebImageDefine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageDefine.m; 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 = ""; }; 329A18571FFF5DFD008C9A2F /* UIImage+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+WebCache.h"; path = "SDWebImage/UIImage+WebCache.h"; sourceTree = ""; }; @@ -1659,6 +1677,8 @@ children = ( 329A18571FFF5DFD008C9A2F /* UIImage+WebCache.h */, 329A18581FFF5DFD008C9A2F /* UIImage+WebCache.m */, + 321DB35F2011D4D60015D2CB /* NSButton+WebCache.h */, + 321DB3602011D4D60015D2CB /* NSButton+WebCache.m */, 535699B415113E7300A4C397 /* MKAnnotationView+WebCache.h */, 535699B515113E7300A4C397 /* MKAnnotationView+WebCache.m */, 53922D93148C56230056699D /* UIButton+WebCache.h */, @@ -1834,6 +1854,8 @@ 53922D92148C56230056699D /* SDWebImagePrefetcher.m */, 324DF4B2200A14DC008A84CC /* SDWebImageDefine.h */, 324DF4B3200A14DC008A84CC /* SDWebImageDefine.m */, + 325312C6200F09910046BF1E /* SDWebImageTransition.h */, + 325312C7200F09910046BF1E /* SDWebImageTransition.m */, ); name = Utils; sourceTree = ""; @@ -2063,6 +2085,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 */, @@ -2133,6 +2156,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 */, @@ -2211,6 +2235,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 */, @@ -2316,10 +2341,12 @@ 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 */, 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 */, @@ -2380,6 +2407,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 */, @@ -2420,6 +2448,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 */, @@ -2772,6 +2801,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 */, @@ -2936,6 +2966,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 */, @@ -3082,6 +3113,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 */, @@ -3233,6 +3265,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 */, @@ -3274,6 +3307,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 */, @@ -3361,6 +3395,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 */, @@ -3511,6 +3546,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/FLAnimatedImage/FLAnimatedImageView+WebCache.m b/SDWebImage/FLAnimatedImage/FLAnimatedImageView+WebCache.m index eb302fbd..773934fe 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) { diff --git a/SDWebImage/NSButton+WebCache.h b/SDWebImage/NSButton+WebCache.h new file mode 100644 index 00000000..57f7115e --- /dev/null +++ b/SDWebImage/NSButton+WebCache.h @@ -0,0 +1,259 @@ +/* + * 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 + +/** + * Get the current image URL. + */ +- (nullable NSURL *)sd_currentImageURL; + +/** + * 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 + +/** + * Get the current alternateImage URL. + */ +- (nullable NSURL *)sd_currentAlternateImageURL; + +/** + * 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; + +#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 new file mode 100644 index 00000000..3ca080f2 --- /dev/null +++ b/SDWebImage/NSButton+WebCache.m @@ -0,0 +1,147 @@ +/* + * 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 "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 + +- (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_currentImageURL = url; + + __weak typeof(self)weakSelf = self; + [self sd_internalSetImageWithURL:url + placeholderImage:placeholder + options:options + operationKey:imageOperationKey() + setImageBlock:^(NSImage * _Nullable image, NSData * _Nullable imageData) { + weakSelf.image = image; + } + 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 { + self.sd_currentAlternateImageURL = url; + + __weak typeof(self)weakSelf = self; + [self sd_internalSetImageWithURL:url + placeholderImage:placeholder + options:options + operationKey:alternateImageOperationKey() + setImageBlock:^(NSImage * _Nullable image, NSData * _Nullable imageData) { + weakSelf.alternateImage = image; + } + progress:progressBlock + completed:completedBlock]; +} + +#pragma mark - Cancel + +- (void)sd_cancelCurrentImageLoad { + [self sd_cancelImageLoadOperationWithKey:imageOperationKey()]; +} + +- (void)sd_cancelCurrentAlternateImageLoad { + [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 + +#endif diff --git a/SDWebImage/SDImageCache.m b/SDWebImage/SDImageCache.m index 25e75c27..bbcc7db8 100644 --- a/SDWebImage/SDImageCache.m +++ b/SDWebImage/SDImageCache.m @@ -207,7 +207,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { } data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format]; } - [self safeStoreImageDataToDisk:data forKey:key error:&writeError]; + [self _storeImageDataToDisk:data forKey:key error:&writeError]; } if (completionBlock) { @@ -236,16 +236,15 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { __block BOOL success = YES; void(^storeImageDataBlock)(void) = ^{ - success = [self safeStoreImageDataToDisk:imageData forKey:key error:error]; + success = [self _storeImageDataToDisk:imageData forKey:key error:error]; }; dispatch_sync(self.ioQueue, storeImageDataBlock); return success; } -- (BOOL)safeStoreImageDataToDisk:(nullable NSData *)imageData - forKey:(nullable NSString *)key - error:(NSError * _Nullable __autoreleasing * _Nonnull)error { +// Make sure to call form io queue by caller +- (BOOL)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key error:(NSError * _Nullable __autoreleasing * _Nonnull)error { if (!imageData || !key) { return NO; } @@ -278,9 +277,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 = [self diskImageDataExistsWithKey:key]; - + dispatch_async(self.ioQueue, ^{ + BOOL exists = [self _diskImageDataExistsWithKey:key]; if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(exists); @@ -293,6 +291,20 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { 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 diff --git a/SDWebImage/SDWebImageDownloader.m b/SDWebImage/SDWebImageDownloader.m index 78ca83a1..0cab689e 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]; @@ -238,11 +243,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 @@ -257,17 +266,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]; @@ -288,35 +304,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) { diff --git a/SDWebImage/SDWebImageManager.h b/SDWebImage/SDWebImageManager.h index 42ee508f..b4a2b2c3 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..06d48d1e --- /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 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. + +@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/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..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]; } @@ -87,20 +95,20 @@ 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)]; + 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]; } @@ -146,15 +154,19 @@ static inline NSString * backgroundImageURLKeyForState(UIControlState state) { completed:completedBlock]; } +#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]; diff --git a/SDWebImage/UIView+WebCache.h b/SDWebImage/UIView+WebCache.h index 530138df..37c39f35 100644 --- a/SDWebImage/UIView+WebCache.h +++ b/SDWebImage/UIView+WebCache.h @@ -12,6 +12,7 @@ #import "SDWebImageDefine.h" #import "SDWebImageManager.h" +#import "SDWebImageTransition.h" /** The value specify that the image progress unit count cannot be determined because the progressBlock is not been called. @@ -97,6 +98,14 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima */ - (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 8461a65d..182917f4 100644 --- a/SDWebImage/UIView+WebCache.m +++ b/SDWebImage/UIView+WebCache.m @@ -145,11 +145,17 @@ 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:SDWebImageContextSetImageGroup]) { dispatch_group_t group = [context valueForKey:SDWebImageContextSetImageGroup]; 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(), ^{ @@ -157,7 +163,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(); }); } @@ -179,24 +185,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 *setImage, NSData *setImageData) { + imageView.image = setImage; + }; + } +#endif +#if SD_UIKIT + else if ([view isKindOfClass:[UIButton class]]) { + UIButton *button = (UIButton *)view; + finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData){ + [button setImage:setImage forState:UIControlStateNormal]; + }; } #endif + if (transition) { #if SD_UIKIT - 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 { @@ -207,6 +268,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 0f74183e..b899118e 100644 --- a/WebImage/SDWebImage.h +++ b/WebImage/SDWebImage.h @@ -35,6 +35,7 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[]; #import #import #import +#import #if SD_MAC || SD_UIKIT #import @@ -55,6 +56,7 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[]; #if SD_MAC #import + #import #endif #if SD_UIKIT