Просмотр исходного кода

Merge pull request #2185 from dreampiggy/refactor_indicator

Refactor the image indicator usage for UIView category
DreamPiggy 8 лет назад
Родитель
Сommit
7f2ba8cded

+ 5 - 50
Examples/SDWebImage Demo/DetailViewController.m

@@ -7,64 +7,25 @@
  */
 
 #import "DetailViewController.h"
-#import <SDWebImage/FLAnimatedImageView.h>
+#import <SDWebImage/UIView+WebCache.h>
 #import <SDWebImage/FLAnimatedImageView+WebCache.h>
 
 @interface DetailViewController ()
 
 @property (strong, nonatomic) IBOutlet FLAnimatedImageView *imageView;
-@property (strong, nonatomic) UIActivityIndicatorView *activityIndicator;
-@property (strong, nonatomic) UIProgressView *progressView;
 
 @end
 
 @implementation DetailViewController
 
-- (UIActivityIndicatorView *)activityIndicator
-{
-    if (!_activityIndicator) {
-        _activityIndicator = [UIActivityIndicatorView.alloc initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
-        _activityIndicator.center = self.imageView.center;
-        _activityIndicator.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
-        [self.imageView addSubview:_activityIndicator];
-        
-    }
-    return _activityIndicator;
-}
-
-- (UIProgressView *)progressView
-{
-    if (!_progressView) {
-        _progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
-        [self.view addSubview:_progressView];
-    }
-    return _progressView;
-}
-
 - (void)configureView
 {
-    self.activityIndicator.hidden = NO;
-    [self.activityIndicator startAnimating];
-    
-    __weak typeof(self) weakSelf = self;
+    if (!self.imageView.sd_imageIndicator) {
+        self.imageView.sd_imageIndicator = SDWebImageProgressIndicator.defaultIndicator;
+    }
     [self.imageView sd_setImageWithURL:self.imageURL
                       placeholderImage:nil
-                               options:SDWebImageProgressiveDownload
-                              progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL *targetURL) {
-                                  dispatch_async(dispatch_get_main_queue(), ^{
-                                      float progress = 0;
-                                      if (expectedSize != 0) {
-                                          progress = (float)receivedSize / (float)expectedSize;
-                                      }
-                                      weakSelf.progressView.hidden = NO;
-                                      [weakSelf.progressView setProgress:progress animated:YES];
-                                  });
-                              }
-                             completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
-                                 weakSelf.progressView.hidden = YES;
-                                 [weakSelf.activityIndicator stopAnimating];
-                                 weakSelf.activityIndicator.hidden = YES;
-                             }];
+                               options:SDWebImageProgressiveDownload];
 }
 
 - (void)viewDidLoad
@@ -73,12 +34,6 @@
     [self configureView];
 }
 
-- (void)viewDidLayoutSubviews
-{
-    [super viewDidLayoutSubviews];
-    self.progressView.frame = CGRectMake(0, self.topLayoutGuide.length, CGRectGetWidth(self.view.bounds), 2.0);
-}
-
 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
 {
     return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);

+ 1 - 3
Examples/SDWebImage Demo/MasterViewController.m

@@ -118,10 +118,8 @@
     if (cell == nil) {
         cell = [[MyCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
         cell.customImageView.sd_imageTransition = SDWebImageTransition.fadeTransition;
+        cell.customImageView.sd_imageIndicator = SDWebImageActivityIndicator.grayIndicator;
     }
-
-    [cell.customImageView sd_setShowActivityIndicatorView:YES];
-    [cell.customImageView sd_setIndicatorStyle:UIActivityIndicatorViewStyleGray];
     
     cell.customTextLabel.text = [NSString stringWithFormat:@"Image #%ld", (long)indexPath.row];
     [cell.customImageView sd_setImageWithURL:[NSURL URLWithString:self.objects[indexPath.row]]

+ 1 - 0
Examples/SDWebImage OSX Demo/ViewController.m

@@ -31,6 +31,7 @@
     // For animated GIF rendering, set `animates` to YES or will only show the first frame
     self.imageView1.animates = YES;
     self.imageView3.animates = YES;
+    self.imageView1.sd_imageIndicator = SDWebImageProgressIndicator.defaultIndicator;
     [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"]];

+ 28 - 0
SDWebImage.xcodeproj/project.pbxproj

@@ -362,6 +362,18 @@
 		329A18621FFF5DFD008C9A2F /* UIImage+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 329A18581FFF5DFD008C9A2F /* UIImage+WebCache.m */; };
 		329A18631FFF5DFD008C9A2F /* UIImage+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 329A18581FFF5DFD008C9A2F /* UIImage+WebCache.m */; };
 		329A18641FFF5DFD008C9A2F /* UIImage+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 329A18581FFF5DFD008C9A2F /* UIImage+WebCache.m */; };
+		32C0FDE12013426C001B8F2D /* SDWebImageIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 32C0FDDF2013426C001B8F2D /* SDWebImageIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		32C0FDE22013426C001B8F2D /* SDWebImageIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 32C0FDDF2013426C001B8F2D /* SDWebImageIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		32C0FDE32013426C001B8F2D /* SDWebImageIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 32C0FDDF2013426C001B8F2D /* SDWebImageIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		32C0FDE42013426C001B8F2D /* SDWebImageIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 32C0FDDF2013426C001B8F2D /* SDWebImageIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		32C0FDE52013426C001B8F2D /* SDWebImageIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 32C0FDDF2013426C001B8F2D /* SDWebImageIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		32C0FDE62013426C001B8F2D /* SDWebImageIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 32C0FDDF2013426C001B8F2D /* SDWebImageIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		32C0FDE72013426C001B8F2D /* SDWebImageIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 32C0FDE02013426C001B8F2D /* SDWebImageIndicator.m */; };
+		32C0FDE82013426C001B8F2D /* SDWebImageIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 32C0FDE02013426C001B8F2D /* SDWebImageIndicator.m */; };
+		32C0FDE92013426C001B8F2D /* SDWebImageIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 32C0FDE02013426C001B8F2D /* SDWebImageIndicator.m */; };
+		32C0FDEA2013426C001B8F2D /* SDWebImageIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 32C0FDE02013426C001B8F2D /* SDWebImageIndicator.m */; };
+		32C0FDEB2013426C001B8F2D /* SDWebImageIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 32C0FDE02013426C001B8F2D /* SDWebImageIndicator.m */; };
+		32C0FDEC2013426C001B8F2D /* SDWebImageIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 32C0FDE02013426C001B8F2D /* SDWebImageIndicator.m */; };
 		32CF1C071FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		32CF1C081FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		32CF1C091FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -1371,6 +1383,8 @@
 		3290FA031FA478AF0047D20C /* SDWebImageFrame.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageFrame.m; sourceTree = "<group>"; };
 		329A18571FFF5DFD008C9A2F /* UIImage+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+WebCache.h"; path = "SDWebImage/UIImage+WebCache.h"; sourceTree = "<group>"; };
 		329A18581FFF5DFD008C9A2F /* UIImage+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+WebCache.m"; path = "SDWebImage/UIImage+WebCache.m"; sourceTree = "<group>"; };
+		32C0FDDF2013426C001B8F2D /* SDWebImageIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageIndicator.h; sourceTree = "<group>"; };
+		32C0FDE02013426C001B8F2D /* SDWebImageIndicator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageIndicator.m; sourceTree = "<group>"; };
 		32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageCoderHelper.h; sourceTree = "<group>"; };
 		32CF1C061FA496B000004BD1 /* SDWebImageCoderHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageCoderHelper.m; sourceTree = "<group>"; };
 		4314D1991D0E0E3B004B36C9 /* libSDWebImage watchOS static.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libSDWebImage watchOS static.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1856,6 +1870,8 @@
 				324DF4B3200A14DC008A84CC /* SDWebImageDefine.m */,
 				325312C6200F09910046BF1E /* SDWebImageTransition.h */,
 				325312C7200F09910046BF1E /* SDWebImageTransition.m */,
+				32C0FDDF2013426C001B8F2D /* SDWebImageIndicator.h */,
+				32C0FDE02013426C001B8F2D /* SDWebImageIndicator.m */,
 			);
 			name = Utils;
 			sourceTree = "<group>";
@@ -2105,6 +2121,7 @@
 				323F8BF91F38EF770092B609 /* animi.h in Headers */,
 				80377C4F1F2F666300F89830 /* filters_utils.h in Headers */,
 				80377C4C1F2F666300F89830 /* color_cache_utils.h in Headers */,
+				32C0FDE42013426C001B8F2D /* SDWebImageIndicator.h in Headers */,
 				321E60C11F38E91700405457 /* UIImage+ForceDecode.h in Headers */,
 				80377DBE1F2F66A700F89830 /* dsp.h in Headers */,
 				80377EB81F2F66D400F89830 /* alphai_dec.h in Headers */,
@@ -2163,6 +2180,7 @@
 				80377C1B1F2F666300F89830 /* filters_utils.h in Headers */,
 				323F8B6F1F38EF770092B609 /* delta_palettization_enc.h in Headers */,
 				4314D1781D0E0E3B004B36C9 /* SDWebImageDownloader.h in Headers */,
+				32C0FDE22013426C001B8F2D /* SDWebImageIndicator.h in Headers */,
 				80377E981F2F66D400F89830 /* alphai_dec.h in Headers */,
 				4314D1791D0E0E3B004B36C9 /* SDWebImageManager.h in Headers */,
 				323F8BE51F38EF770092B609 /* vp8li_enc.h in Headers */,
@@ -2243,6 +2261,7 @@
 				80377C671F2F666400F89830 /* endian_inl_utils.h in Headers */,
 				80377C6B1F2F666400F89830 /* huffman_encode_utils.h in Headers */,
 				323F8C121F38EF770092B609 /* muxi.h in Headers */,
+				32C0FDE52013426C001B8F2D /* SDWebImageIndicator.h in Headers */,
 				80377ED61F2F66D500F89830 /* webpi_dec.h in Headers */,
 				323F8BE81F38EF770092B609 /* vp8li_enc.h in Headers */,
 				323F8B8A1F38EF770092B609 /* histogram_enc.h in Headers */,
@@ -2326,6 +2345,7 @@
 				80377E3A1F2F66A800F89830 /* common_sse2.h in Headers */,
 				4397D2D91D0DDD8C00BB2784 /* SDWebImagePrefetcher.h in Headers */,
 				80377C871F2F666400F89830 /* huffman_utils.h in Headers */,
+				32C0FDE62013426C001B8F2D /* SDWebImageIndicator.h in Headers */,
 				4397D2DA1D0DDD8C00BB2784 /* UIView+WebCacheOperation.h in Headers */,
 				80377E661F2F66A800F89830 /* neon.h in Headers */,
 				4397D2DB1D0DDD8C00BB2784 /* UIImage+MultiFormat.h in Headers */,
@@ -2427,6 +2447,7 @@
 				80377C351F2F666300F89830 /* filters_utils.h in Headers */,
 				80377C321F2F666300F89830 /* color_cache_utils.h in Headers */,
 				321E60C01F38E91700405457 /* UIImage+ForceDecode.h in Headers */,
+				32C0FDE32013426C001B8F2D /* SDWebImageIndicator.h in Headers */,
 				80377D791F2F66A700F89830 /* dsp.h in Headers */,
 				80377EA81F2F66D400F89830 /* alphai_dec.h in Headers */,
 				4A2CAE2D1AB4BB7500B6BC39 /* UIImage+GIF.h in Headers */,
@@ -2449,6 +2470,7 @@
 				53761316155AD0D5005750A4 /* SDImageCache.h in Headers */,
 				323F8C0E1F38EF770092B609 /* muxi.h in Headers */,
 				325312C8200F09910046BF1E /* SDWebImageTransition.h in Headers */,
+				32C0FDE12013426C001B8F2D /* SDWebImageIndicator.h in Headers */,
 				321E60A21F38E8F600405457 /* SDWebImageGIFCoder.h in Headers */,
 				5D5B9142188EE8DD006D06BD /* NSData+ImageContentType.h in Headers */,
 				80377BFE1F2F665300F89830 /* color_cache_utils.h in Headers */,
@@ -2779,6 +2801,7 @@
 				321E60B91F38E90100405457 /* SDWebImageWebPCoder.m in Sources */,
 				80377DEB1F2F66A700F89830 /* yuv.c in Sources */,
 				3237F9E920161AE000A88143 /* NSImage+Additions.m in Sources */,
+				32C0FDEA2013426C001B8F2D /* SDWebImageIndicator.m in Sources */,
 				00733A551BC4880000A5A117 /* SDWebImageDownloader.m in Sources */,
 				80377EB71F2F66D400F89830 /* alpha_dec.c in Sources */,
 				80377DC61F2F66A700F89830 /* enc.c in Sources */,
@@ -2980,6 +3003,7 @@
 				323F8BA91F38EF770092B609 /* picture_psnr_enc.c in Sources */,
 				323F8C091F38EF770092B609 /* muxedit.c in Sources */,
 				80377D1F1F2F66A700F89830 /* alpha_processing_neon.c in Sources */,
+				32C0FDE82013426C001B8F2D /* SDWebImageIndicator.m in Sources */,
 				4314D1401D0E0E3B004B36C9 /* UIImageView+WebCache.m in Sources */,
 				43A9186C1D8308FE00B3925F /* SDImageCacheConfig.m in Sources */,
 				3237F9EC20161AE000A88143 /* NSImage+Additions.m in Sources */,
@@ -3127,6 +3151,7 @@
 				80377C611F2F666400F89830 /* bit_reader_utils.c in Sources */,
 				323F8BAC1F38EF770092B609 /* picture_psnr_enc.c in Sources */,
 				323F8C0C1F38EF770092B609 /* muxedit.c in Sources */,
+				32C0FDEB2013426C001B8F2D /* SDWebImageIndicator.m in Sources */,
 				80377DEE1F2F66A800F89830 /* alpha_processing_neon.c in Sources */,
 				43C892A41D9D6DDD0022038D /* demux.c in Sources */,
 				3237F9EA20161AE000A88143 /* NSImage+Additions.m in Sources */,
@@ -3312,6 +3337,7 @@
 				4369C2831D9807EC007E863A /* UIView+WebCache.m in Sources */,
 				80377E611F2F66A800F89830 /* lossless_sse2.c in Sources */,
 				80377E691F2F66A800F89830 /* rescaler_msa.c in Sources */,
+				32C0FDEC2013426C001B8F2D /* SDWebImageIndicator.m in Sources */,
 				80377E5E1F2F66A800F89830 /* lossless_mips_dsp_r2.c in Sources */,
 				80377E3D1F2F66A800F89830 /* cost_sse2.c in Sources */,
 				321E60BB1F38E90100405457 /* SDWebImageWebPCoder.m in Sources */,
@@ -3373,6 +3399,7 @@
 				321E60B81F38E90100405457 /* SDWebImageWebPCoder.m in Sources */,
 				43CE757A1CFE9427006C64D0 /* FLAnimatedImage.m in Sources */,
 				3237F9E820161AE000A88143 /* NSImage+Additions.m in Sources */,
+				32C0FDE92013426C001B8F2D /* SDWebImageIndicator.m in Sources */,
 				80377D811F2F66A700F89830 /* enc.c in Sources */,
 				80377EA71F2F66D400F89830 /* alpha_dec.c in Sources */,
 				80377D8F1F2F66A700F89830 /* lossless_mips_dsp_r2.c in Sources */,
@@ -3524,6 +3551,7 @@
 				43CE75791CFE9427006C64D0 /* FLAnimatedImage.m in Sources */,
 				80377CF71F2F66A100F89830 /* enc.c in Sources */,
 				3237F9EB20161AE000A88143 /* NSImage+Additions.m in Sources */,
+				32C0FDE72013426C001B8F2D /* SDWebImageIndicator.m in Sources */,
 				80377E871F2F66D000F89830 /* alpha_dec.c in Sources */,
 				80377D051F2F66A100F89830 /* lossless_mips_dsp_r2.c in Sources */,
 				80377C0A1F2F665300F89830 /* random_utils.c in Sources */,

+ 97 - 0
SDWebImage/SDWebImageIndicator.h

@@ -0,0 +1,97 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import "SDWebImageCompat.h"
+
+#if SD_UIKIT || SD_MAC
+
+// A protocol to custom the indicator during the image loading
+// All of these methods are called from main queue
+@protocol SDWebImageIndicator <NSObject>
+
+@required
+/**
+ The view associate to the indicator.
+
+ @return The indicator view
+ */
+- (nonnull UIView *)indicatorView;
+/**
+ Start the animating for indicator.
+ */
+- (void)startAnimatingIndicator;
+/**
+ Stop the animating for indicator.
+ */
+- (void)stopAnimatingIndicator;
+
+@optional
+/**
+ Update the loading progress (0-1.0) for indicator. Optional
+ 
+ @param progress The progress, value between 0 and 1.0
+ */
+- (void)updateIndicatorProgress:(double)progress;
+
+@end
+
+#pragma mark - Activity Indicator
+
+// Activity indicator class
+// for UIKit(macOS), it use a `UIActivityIndicatorView`
+// for AppKit(macOS), it use a `NSProgressIndicator` with the spinning style
+@interface SDWebImageActivityIndicator : NSObject <SDWebImageIndicator>
+
+#if SD_UIKIT
+@property (nonatomic, strong, readonly, nonnull) UIActivityIndicatorView *indicatorView;
+#else
+@property (nonatomic, strong, readonly, nonnull) NSProgressIndicator *indicatorView;
+#endif
+
+@end
+
+// Convenience way to use activity indicator.
+@interface SDWebImageActivityIndicator (Conveniences)
+
+/// gray-style activity indicator
+@property (nonatomic, class, nonnull, readonly) SDWebImageActivityIndicator *grayIndicator API_UNAVAILABLE(tvos);
+/// large gray-style activity indicator
+@property (nonatomic, class, nonnull, readonly) SDWebImageActivityIndicator *grayLargeIndicator API_UNAVAILABLE(ios, tvos);
+/// white-style activity indicator
+@property (nonatomic, class, nonnull, readonly) SDWebImageActivityIndicator *whiteIndicator;
+/// large white-style activity indicator
+@property (nonatomic, class, nonnull, readonly) SDWebImageActivityIndicator *whiteLargeIndicator;
+
+@end
+
+#pragma mark - Progress Indicator
+
+// Progress indicator class
+// for UIKit(macOS), it use a `UIProgressView`
+// for AppKit(macOS), it use a `NSProgressIndicator` with the bar style
+@interface SDWebImageProgressIndicator : NSObject <SDWebImageIndicator>
+
+#if SD_UIKIT
+@property (nonatomic, strong, readonly, nonnull) UIProgressView *indicatorView;
+#else
+@property (nonatomic, strong, readonly, nonnull) NSProgressIndicator *indicatorView;
+#endif
+
+@end
+
+// Convenience way to create progress indicator. Remember to specify the indicator width or use layout constraint if need.
+@interface SDWebImageProgressIndicator (Conveniences)
+
+/// default-style progress indicator
+@property (nonatomic, class, nonnull, readonly) SDWebImageProgressIndicator *defaultIndicator;
+/// bar-style progress indicator
+@property (nonatomic, class, nonnull, readonly) SDWebImageProgressIndicator *barIndicator API_UNAVAILABLE(macos, tvos);
+
+@end
+
+#endif

+ 226 - 0
SDWebImage/SDWebImageIndicator.m

@@ -0,0 +1,226 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import "SDWebImageIndicator.h"
+
+#if SD_UIKIT || SD_MAC
+
+#if SD_MAC
+#import <QuartzCore/QuartzCore.h>
+#endif
+
+#pragma mark - Activity Indicator
+
+@interface SDWebImageActivityIndicator ()
+
+#if SD_UIKIT
+@property (nonatomic, strong, readwrite, nonnull) UIActivityIndicatorView *indicatorView;
+#else
+@property (nonatomic, strong, readwrite, nonnull) NSProgressIndicator *indicatorView;
+#endif
+
+@end
+
+@implementation SDWebImageActivityIndicator
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        [self commonInit];
+    }
+    return self;
+}
+
+#if SD_UIKIT
+- (void)commonInit {
+#if SD_TV
+    UIActivityIndicatorViewStyle style = UIActivityIndicatorViewStyleWhite;
+#else
+    UIActivityIndicatorViewStyle style = UIActivityIndicatorViewStyleGray;
+#endif
+    self.indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:style];
+    self.indicatorView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
+}
+#endif
+
+#if SD_MAC
+- (void)commonInit {
+    self.indicatorView = [[NSProgressIndicator alloc] initWithFrame:NSZeroRect];
+    self.indicatorView.style = NSProgressIndicatorStyleSpinning;
+    self.indicatorView.controlSize = NSControlSizeSmall;
+    [self.indicatorView sizeToFit];
+    self.indicatorView.autoresizingMask = NSViewMaxXMargin | NSViewMinXMargin | NSViewMaxYMargin | NSViewMinYMargin;
+}
+#endif
+
+- (void)startAnimatingIndicator {
+#if SD_UIKIT
+    [self.indicatorView startAnimating];
+#else
+    [self.indicatorView startAnimation:nil];
+#endif
+    self.indicatorView.hidden = NO;
+}
+
+- (void)stopAnimatingIndicator {
+#if SD_UIKIT
+    [self.indicatorView stopAnimating];
+#else
+    [self.indicatorView stopAnimation:nil];
+#endif
+    self.indicatorView.hidden = YES;
+}
+
+@end
+
+@implementation SDWebImageActivityIndicator (Conveniences)
+
+#if SD_MAC || SD_IOS
++ (SDWebImageActivityIndicator *)grayIndicator {
+    SDWebImageActivityIndicator *indicator = [SDWebImageActivityIndicator new];
+    return indicator;
+}
+#endif
+
+#if SD_MAC
++ (SDWebImageActivityIndicator *)grayLargeIndicator {
+    SDWebImageActivityIndicator *indicator = SDWebImageActivityIndicator.grayIndicator;
+    indicator.indicatorView.controlSize = NSControlSizeRegular;
+    [indicator.indicatorView sizeToFit];
+    return indicator;
+}
+#endif
+
++ (SDWebImageActivityIndicator *)whiteIndicator {
+    SDWebImageActivityIndicator *indicator = [SDWebImageActivityIndicator new];
+#if SD_UIKIT
+    indicator.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhite;
+#else
+    CIFilter *lighten = [CIFilter filterWithName:@"CIColorControls"];
+    [lighten setDefaults];
+    [lighten setValue:@(1) forKey:kCIInputBrightnessKey];
+    indicator.indicatorView.contentFilters = @[lighten];
+#endif
+    return indicator;
+}
+
++ (SDWebImageActivityIndicator *)whiteLargeIndicator {
+    SDWebImageActivityIndicator *indicator = SDWebImageActivityIndicator.whiteIndicator;
+#if SD_UIKIT
+    indicator.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
+#else
+    indicator.indicatorView.controlSize = NSControlSizeRegular;
+    [indicator.indicatorView sizeToFit];
+#endif
+    return indicator;
+}
+
+@end
+
+#pragma mark - Progress Indicator
+
+@interface SDWebImageProgressIndicator ()
+
+#if SD_UIKIT
+@property (nonatomic, strong, readwrite, nonnull) UIProgressView *indicatorView;
+#else
+@property (nonatomic, strong, readwrite, nonnull) NSProgressIndicator *indicatorView;
+#endif
+
+@end
+
+@implementation SDWebImageProgressIndicator
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        [self commonInit];
+    }
+    return self;
+}
+
+#if SD_UIKIT
+- (void)commonInit {
+    self.indicatorView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
+    self.indicatorView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
+}
+#endif
+
+#if SD_MAC
+- (void)commonInit {
+    self.indicatorView = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0, 0, 160, 0)];
+    self.indicatorView.style = NSProgressIndicatorStyleBar;
+    self.indicatorView.controlSize = NSControlSizeSmall;
+    [self.indicatorView sizeToFit];
+    self.indicatorView.autoresizingMask = NSViewMaxXMargin | NSViewMinXMargin | NSViewMaxYMargin | NSViewMinYMargin;
+}
+#endif
+
+- (void)startAnimatingIndicator {
+    self.indicatorView.hidden = NO;
+#if SD_UIKIT
+    if ([self.indicatorView respondsToSelector:@selector(observedProgress)] && self.indicatorView.observedProgress) {
+        // Ignore NSProgress
+    } else {
+        self.indicatorView.progress = 0;
+    }
+#else
+    self.indicatorView.indeterminate = YES;
+    self.indicatorView.doubleValue = 0;
+    [self.indicatorView startAnimation:nil];
+#endif
+}
+
+- (void)stopAnimatingIndicator {
+    self.indicatorView.hidden = YES;
+#if SD_UIKIT
+    if ([self.indicatorView respondsToSelector:@selector(observedProgress)] && self.indicatorView.observedProgress) {
+        // Ignore NSProgress
+    } else {
+        self.indicatorView.progress = 1;
+    }
+#else
+    self.indicatorView.indeterminate = NO;
+    self.indicatorView.doubleValue = 1;
+    [self.indicatorView stopAnimation:nil];
+#endif
+}
+
+- (void)updateIndicatorProgress:(double)progress {
+#if SD_UIKIT
+    if ([self.indicatorView respondsToSelector:@selector(observedProgress)] && self.indicatorView.observedProgress) {
+        // Ignore NSProgress
+    } else {
+        [self.indicatorView setProgress:progress animated:YES];
+    }
+#else
+    self.indicatorView.indeterminate = progress > 0 ? NO : YES;
+    self.indicatorView.doubleValue = progress * 100;
+#endif
+}
+
+@end
+
+@implementation SDWebImageProgressIndicator (Conveniences)
+
++ (SDWebImageProgressIndicator *)defaultIndicator {
+    SDWebImageProgressIndicator *indicator = [SDWebImageProgressIndicator new];
+    return indicator;
+}
+
+#if SD_IOS
++ (SDWebImageProgressIndicator *)barIndicator {
+    SDWebImageProgressIndicator *indicator = [SDWebImageProgressIndicator new];
+    indicator.indicatorView.progressViewStyle = UIProgressViewStyleBar;
+    return indicator;
+}
+#endif
+
+@end
+
+#endif

+ 6 - 18
SDWebImage/UIView+WebCache.h

@@ -13,6 +13,7 @@
 #import "SDWebImageDefine.h"
 #import "SDWebImageManager.h"
 #import "SDWebImageTransition.h"
+#import "SDWebImageIndicator.h"
 
 /**
  The value specify that the image progress unit count cannot be determined because the progressBlock is not been called.
@@ -106,27 +107,14 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima
  */
 @property (nonatomic, strong, nullable) SDWebImageTransition *sd_imageTransition;
 
-#if SD_UIKIT
-
-#pragma mark - Activity indicator
-
-/**
- *  Show activity UIActivityIndicatorView
- */
-- (void)sd_setShowActivityIndicatorView:(BOOL)show;
+#pragma mark - Image Indicator
 
 /**
- *  set desired UIActivityIndicatorViewStyle
- *
- *  @param style The style of the UIActivityIndicatorView
+ The image indicator during the image loading. If you do not need indicator, specify nil. Defaults to nil
+ The setter will remove the old indicator view and add new indicator view to current view's subview.
+ @note Because this is UI related, you should access only from the main queue.
  */
-- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style;
-
-- (BOOL)sd_showActivityIndicatorView;
-- (void)sd_addActivityIndicator;
-- (void)sd_removeActivityIndicator;
-
-#endif
+@property (nonatomic, strong, nullable) id<SDWebImageIndicator> sd_imageIndicator;
 
 @end
 

+ 55 - 71
SDWebImage/UIView+WebCache.m

@@ -17,12 +17,6 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
 
 static char imageURLKey;
 
-#if SD_UIKIT
-static char TAG_ACTIVITY_INDICATOR;
-static char TAG_ACTIVITY_STYLE;
-#endif
-static char TAG_ACTIVITY_SHOW;
-
 @implementation UIView (WebCache)
 
 - (nullable NSURL *)sd_imageURL {
@@ -79,15 +73,13 @@ static char TAG_ACTIVITY_SHOW;
     }
     
     if (url) {
-        // check if activityView is enabled or not
-        if ([self sd_showActivityIndicatorView]) {
-            [self sd_addActivityIndicator];
-        }
-        
         // reset the progress
         self.sd_imageProgress.totalUnitCount = 0;
         self.sd_imageProgress.completedUnitCount = 0;
         
+        // check and start image indicator
+        [self sd_startImageIndicator];
+        
         SDWebImageManager *manager;
         if ([context valueForKey:SDWebImageContextCustomManager]) {
             manager = (SDWebImageManager *)[context valueForKey:SDWebImageContextCustomManager];
@@ -95,6 +87,8 @@ static char TAG_ACTIVITY_SHOW;
             manager = [SDWebImageManager sharedManager];
         }
         
+        id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
+        
         __weak __typeof(self)wself = self;
         SDWebImageDownloaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
             wself.sd_imageProgress.totalUnitCount = expectedSize;
@@ -102,16 +96,27 @@ static char TAG_ACTIVITY_SHOW;
             if (progressBlock) {
                 progressBlock(receivedSize, expectedSize, targetURL);
             }
+            if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
+                double progress = wself.sd_imageProgress.fractionCompleted;
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [imageIndicator updateIndicatorProgress:progress];
+                });
+            }
         };
         id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
             __strong __typeof (wself) sself = wself;
             if (!sself) { return; }
-            [sself sd_removeActivityIndicator];
             // if the progress not been updated, mark it to complete state
             if (finished && !error && sself.sd_imageProgress.totalUnitCount == 0 && sself.sd_imageProgress.completedUnitCount == 0) {
                 sself.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
                 sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
             }
+            
+            // check and stop image indicator
+            if (finished) {
+                [self sd_stopImageIndicator];
+            }
+            
             BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
             BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                       (!image && !(options & SDWebImageDelayPlaceholder)));
@@ -170,8 +175,8 @@ static char TAG_ACTIVITY_SHOW;
         } context:context];
         [self sd_setImageLoadOperation:operation forKey:validOperationKey];
     } else {
+        [self sd_stopImageIndicator];
         dispatch_main_async_safe(^{
-            [self sd_removeActivityIndicator];
             if (completedBlock) {
                 NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                 completedBlock(nil, error, SDImageCacheTypeNone, url);
@@ -277,75 +282,54 @@ static char TAG_ACTIVITY_SHOW;
     objc_setAssociatedObject(self, @selector(sd_imageTransition), sd_imageTransition, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 }
 
-#pragma mark - Activity indicator
-
-#pragma mark -
-#if SD_UIKIT
-- (UIActivityIndicatorView *)activityIndicator {
-    return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
+#pragma mark - Indicator
+- (id<SDWebImageIndicator>)sd_imageIndicator {
+    return objc_getAssociatedObject(self, @selector(sd_imageIndicator));
 }
 
-- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
-    objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
-}
+- (void)setSd_imageIndicator:(id<SDWebImageIndicator>)sd_imageIndicator {
+    id<SDWebImageIndicator> previousIndicator = self.sd_imageIndicator;
+    if (previousIndicator == sd_imageIndicator) {
+        [previousIndicator.indicatorView removeFromSuperview];
+    }
+    
+    // Add the new indicator view
+    UIView *view = sd_imageIndicator.indicatorView;
+    if (CGRectEqualToRect(view.frame, CGRectZero)) {
+        view.frame = self.frame;
+    }
+    // Center the indicator view
+#if SD_MAC
+    CGPoint center = CGPointMake(NSMidX(self.bounds), NSMidY(self.bounds));
+    NSRect frame = view.frame;
+    view.frame = NSMakeRect(center.x - NSMidX(frame), center.y - NSMidY(frame), NSWidth(frame), NSHeight(frame));
+#else
+    view.center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
 #endif
-
-- (void)sd_setShowActivityIndicatorView:(BOOL)show {
-    objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, @(show), OBJC_ASSOCIATION_RETAIN);
-}
-
-- (BOOL)sd_showActivityIndicatorView {
-    return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
-}
-
-#if SD_UIKIT
-- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
-    objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
+    view.hidden = NO;
+    [self addSubview:view];
+    
+    objc_setAssociatedObject(self, @selector(sd_imageIndicator), sd_imageIndicator, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 }
 
-- (int)sd_getIndicatorStyle{
-    return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
-}
-#endif
-
-- (void)sd_addActivityIndicator {
-#if SD_UIKIT
+- (void)sd_startImageIndicator {
+    id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
+    if (!imageIndicator) {
+        return;
+    }
     dispatch_main_async_safe(^{
-        if (!self.activityIndicator) {
-            self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:[self sd_getIndicatorStyle]];
-            self.activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
-        
-            [self addSubview:self.activityIndicator];
-            
-            [self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
-                                                             attribute:NSLayoutAttributeCenterX
-                                                             relatedBy:NSLayoutRelationEqual
-                                                                toItem:self
-                                                             attribute:NSLayoutAttributeCenterX
-                                                            multiplier:1.0
-                                                              constant:0.0]];
-            [self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
-                                                             attribute:NSLayoutAttributeCenterY
-                                                             relatedBy:NSLayoutRelationEqual
-                                                                toItem:self
-                                                             attribute:NSLayoutAttributeCenterY
-                                                            multiplier:1.0
-                                                              constant:0.0]];
-        }
-        [self.activityIndicator startAnimating];
+        [imageIndicator startAnimatingIndicator];
     });
-#endif
 }
 
-- (void)sd_removeActivityIndicator {
-#if SD_UIKIT
+- (void)sd_stopImageIndicator {
+    id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
+    if (!imageIndicator) {
+        return;
+    }
     dispatch_main_async_safe(^{
-        if (self.activityIndicator) {
-            [self.activityIndicator removeFromSuperview];
-            self.activityIndicator = nil;
-        }
+        [imageIndicator stopAnimatingIndicator];
     });
-#endif
 }
 
 @end

+ 1 - 0
WebImage/SDWebImage.h

@@ -36,6 +36,7 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[];
 #import <SDWebImage/SDWebImageOperation.h>
 #import <SDWebImage/SDWebImageDownloader.h>
 #import <SDWebImage/SDWebImageTransition.h>
+#import <SDWebImage/SDWebImageIndicator.h>
 
 #if SD_MAC || SD_UIKIT
     #import <SDWebImage/MKAnnotationView+WebCache.h>