diff --git a/Demo/Kingfisher-Demo/ViewController.swift b/Demo/Kingfisher-Demo/ViewController.swift index 2ec89cc41..0e5e98d4c 100755 --- a/Demo/Kingfisher-Demo/ViewController.swift +++ b/Demo/Kingfisher-Demo/ViewController.swift @@ -62,12 +62,10 @@ extension ViewController { override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! CollectionViewCell - - cell.cellImageView.kf_showIndicatorWhenLoading = true - + + cell.cellImageView.kf_indicatorType = .activity let url = URL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher/master/images/kingfisher-\(indexPath.row + 1).jpg")! - _ = cell.cellImageView.kf_setImage(with: url, placeholder: nil, options: [.transition(ImageTransition.fade(1))], diff --git a/images/loader.gif b/Demo/Kingfisher-Demo/loader.gif similarity index 100% rename from images/loader.gif rename to Demo/Kingfisher-Demo/loader.gif diff --git a/Demo/Kingfisher-macOS-Demo/ViewController.swift b/Demo/Kingfisher-macOS-Demo/ViewController.swift index 44dda26bf..7f756ff6d 100755 --- a/Demo/Kingfisher-macOS-Demo/ViewController.swift +++ b/Demo/Kingfisher-macOS-Demo/ViewController.swift @@ -57,7 +57,7 @@ extension ViewController: NSCollectionViewDataSource { let url = URL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher/master/images/kingfisher-\(indexPath.item + 1).jpg")! - item.imageView?.kf_showIndicatorWhenLoading = true + item.imageView?.kf_indicatorType = .activity item.imageView?.kf_setImage(with: url, placeholder: nil, options: nil, progressBlock: { receivedSize, totalSize in print("\(indexPath.item + 1): \(receivedSize)/\(totalSize)") diff --git a/Kingfisher.xcodeproj/project.pbxproj b/Kingfisher.xcodeproj/project.pbxproj index e04b1eee0..c50e7b4e2 100644 --- a/Kingfisher.xcodeproj/project.pbxproj +++ b/Kingfisher.xcodeproj/project.pbxproj @@ -19,9 +19,18 @@ 4B3766841C478F940001443F /* Kingfisher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D13F49D61BEDA67C00CE335D /* Kingfisher.framework */; }; 4B3766A01C4794460001443F /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B37669F1C4794460001443F /* CFNetwork.framework */; }; 4B3766A21C47944D0001443F /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B3766A11C47944D0001443F /* CFNetwork.framework */; }; + 4B4307A51D87E6A700ED2DA9 /* loader.gif in Resources */ = {isa = PBXBuildFile; fileRef = 4B7742461D87E42E0077024E /* loader.gif */; }; 4B6313F41D766BEF0078E017 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6313F31D766BEF0078E017 /* Filter.swift */; }; 4B6313F51D766BEF0078E017 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6313F31D766BEF0078E017 /* Filter.swift */; }; 4B6313F61D766BEF0078E017 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6313F31D766BEF0078E017 /* Filter.swift */; }; + 4B77423F1D87E08A0077024E /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B77423E1D87E08A0077024E /* Indicator.swift */; }; + 4B7742401D87E08A0077024E /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B77423E1D87E08A0077024E /* Indicator.swift */; }; + 4B7742411D87E08A0077024E /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B77423E1D87E08A0077024E /* Indicator.swift */; }; + 4B7742431D87E2AA0077024E /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7742421D87E2AA0077024E /* Box.swift */; }; + 4B7742441D87E2AA0077024E /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7742421D87E2AA0077024E /* Box.swift */; }; + 4B7742451D87E2AA0077024E /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7742421D87E2AA0077024E /* Box.swift */; }; + 4B7742471D87E42E0077024E /* loader.gif in Resources */ = {isa = PBXBuildFile; fileRef = 4B7742461D87E42E0077024E /* loader.gif */; }; + 4B7742481D87E42E0077024E /* loader.gif in Resources */ = {isa = PBXBuildFile; fileRef = 4B7742461D87E42E0077024E /* loader.gif */; }; 4B98674F1CD1CF42003ADAC7 /* AnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B98674E1CD1CF42003ADAC7 /* AnimatedImageView.swift */; }; 4B9867501CD1CF42003ADAC7 /* AnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B98674E1CD1CF42003ADAC7 /* AnimatedImageView.swift */; }; 4BB24C3D1D79215A00CD5F9C /* CacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB24C3C1D79215A00CD5F9C /* CacheSerializer.swift */; }; @@ -475,6 +484,9 @@ 4B3766A11C47944D0001443F /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; }; 4B3E714D1B01FEB200F5AAED /* WatchKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WatchKit.framework; path = System/Library/Frameworks/WatchKit.framework; sourceTree = SDKROOT; }; 4B6313F31D766BEF0078E017 /* Filter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Filter.swift; path = Sources/Filter.swift; sourceTree = ""; }; + 4B77423E1D87E08A0077024E /* Indicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Indicator.swift; path = Sources/Indicator.swift; sourceTree = ""; }; + 4B7742421D87E2AA0077024E /* Box.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Box.swift; path = Sources/Box.swift; sourceTree = ""; }; + 4B7742461D87E42E0077024E /* loader.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = loader.gif; sourceTree = ""; }; 4B98674E1CD1CF42003ADAC7 /* AnimatedImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AnimatedImageView.swift; path = Sources/AnimatedImageView.swift; sourceTree = ""; }; 4BB24C3C1D79215A00CD5F9C /* CacheSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CacheSerializer.swift; path = Sources/CacheSerializer.swift; sourceTree = ""; }; 4BCCF3361D5B02F8003387C2 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -871,6 +883,7 @@ isa = PBXGroup; children = ( D10945EA1C526B6C001408EB /* Image.swift */, + 4B77423E1D87E08A0077024E /* Indicator.swift */, D10945EB1C526B6C001408EB /* ImageCache.swift */, D10945EC1C526B6C001408EB /* ImageDownloader.swift */, D9638B9F1C7DBA660046523D /* ImagePrefetcher.swift */, @@ -907,6 +920,7 @@ 4BF39F3F1D796BDD0035ECC8 /* Helpers */ = { isa = PBXGroup; children = ( + 4B7742421D87E2AA0077024E /* Box.swift */, D10945F41C526B6C001408EB /* String+MD5.swift */, D10945F51C526B6C001408EB /* ThreadHelper.swift */, ); @@ -1017,6 +1031,7 @@ D12E0C8B1C47F91800AC98AD /* Kingfisher-Demo */ = { isa = PBXGroup; children = ( + 4B7742461D87E42E0077024E /* loader.gif */, D12E0C8C1C47F91800AC98AD /* AppDelegate.swift */, D12E0C8D1C47F91800AC98AD /* LaunchScreen.xib */, D12E0C8F1C47F91800AC98AD /* Main.storyboard */, @@ -1479,6 +1494,7 @@ files = ( 4BCCF33E1D5B02F8003387C2 /* Assets.xcassets in Resources */, 4BCCF3401D5B02F8003387C2 /* Cell.xib in Resources */, + 4B4307A51D87E6A700ED2DA9 /* loader.gif in Resources */, 4BCCF33F1D5B02F8003387C2 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1631,6 +1647,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4B7742481D87E42E0077024E /* loader.gif in Resources */, D12E0CA31C47F92200AC98AD /* Assets.xcassets in Resources */, D12E0CA41C47F92200AC98AD /* Main.storyboard in Resources */, ); @@ -1672,6 +1689,7 @@ buildActionMask = 2147483647; files = ( D12E0C991C47F91800AC98AD /* Images.xcassets in Resources */, + 4B7742471D87E42E0077024E /* loader.gif in Resources */, D12E0C961C47F91800AC98AD /* LaunchScreen.xib in Resources */, D12E0C971C47F91800AC98AD /* Main.storyboard in Resources */, ); @@ -2042,8 +2060,10 @@ D109461D1C526C61001408EB /* ImageTransition.swift in Sources */, 4B2B8E4C1D70141000FC4749 /* ImageProcessor.swift in Sources */, 4BFBEE7F1D7D0C3600699FD3 /* RequrstModifier.swift in Sources */, + 4B7742411D87E08A0077024E /* Indicator.swift in Sources */, D109461E1C526C61001408EB /* ImageView+Kingfisher.swift in Sources */, D109461F1C526C61001408EB /* KingfisherManager.swift in Sources */, + 4B7742451D87E2AA0077024E /* Box.swift in Sources */, 4B6313F61D766BEF0078E017 /* Filter.swift in Sources */, 182FFF781CC9ACBA004B728D /* NSButton+Kingfisher.swift in Sources */, D10946201C526C61001408EB /* KingfisherOptionsInfo.swift in Sources */, @@ -2116,11 +2136,13 @@ files = ( D109460E1C526C0D001408EB /* Image.swift in Sources */, 4BFBEE7E1D7D0C3600699FD3 /* RequrstModifier.swift in Sources */, + 4B7742441D87E2AA0077024E /* Box.swift in Sources */, 4B6313F51D766BEF0078E017 /* Filter.swift in Sources */, 4B9867501CD1CF42003ADAC7 /* AnimatedImageView.swift in Sources */, D109460F1C526C0D001408EB /* ImageCache.swift in Sources */, D10946101C526C0D001408EB /* ImageDownloader.swift in Sources */, 4B2B8E4B1D70140F00FC4749 /* ImageProcessor.swift in Sources */, + 4B7742401D87E08A0077024E /* Indicator.swift in Sources */, D10946111C526C0D001408EB /* ImageTransition.swift in Sources */, D10946121C526C0D001408EB /* ImageView+Kingfisher.swift in Sources */, D10946131C526C0D001408EB /* KingfisherManager.swift in Sources */, @@ -2179,11 +2201,13 @@ files = ( D10945F71C526B86001408EB /* Image.swift in Sources */, 4BFBEE7D1D7D0C3600699FD3 /* RequrstModifier.swift in Sources */, + 4B7742431D87E2AA0077024E /* Box.swift in Sources */, 4B6313F41D766BEF0078E017 /* Filter.swift in Sources */, 4B98674F1CD1CF42003ADAC7 /* AnimatedImageView.swift in Sources */, D10945F81C526B86001408EB /* ImageCache.swift in Sources */, D10945F91C526B86001408EB /* ImageDownloader.swift in Sources */, 4B2B8E4A1D70128200FC4749 /* ImageProcessor.swift in Sources */, + 4B77423F1D87E08A0077024E /* Indicator.swift in Sources */, D10945FA1C526B86001408EB /* ImageTransition.swift in Sources */, D10945FB1C526B86001408EB /* ImageView+Kingfisher.swift in Sources */, D10945FC1C526B86001408EB /* KingfisherManager.swift in Sources */, diff --git a/Sources/ImageView+Kingfisher.swift b/Sources/ImageView+Kingfisher.swift index 114a42fe1..4b1f1dcb7 100755 --- a/Sources/ImageView+Kingfisher.swift +++ b/Sources/ImageView+Kingfisher.swift @@ -28,11 +28,9 @@ #if os(macOS) import AppKit typealias ImageView = NSImageView -public typealias IndicatorView = NSProgressIndicator #else import UIKit typealias ImageView = UIImageView -public typealias IndicatorView = UIActivityIndicatorView #endif // MARK: - Set Images @@ -69,13 +67,8 @@ extension ImageView { return .empty } - let showIndicatorWhenLoading = kf_showIndicatorWhenLoading - var indicator: IndicatorView? = nil - if showIndicatorWhenLoading { - indicator = kf_indicator - indicator?.isHidden = false - indicator?.kf_startAnimating() - } + let maybeIndicator = kf_indicator + maybeIndicator?.startAnimatingView() kf_setWebURL(resource.downloadURL) @@ -100,7 +93,7 @@ extension ImageView { sSelf.kf_setImageTask(nil) guard let image = image else { - indicator?.kf_stopAnimating() + maybeIndicator?.stopAnimatingView() completionHandler?(nil, error, cacheType, imageURL) return } @@ -108,7 +101,7 @@ extension ImageView { guard let transitionItem = options.kf_firstMatchIgnoringAssociatedValue(.transition(.none)), case .transition(let transition) = transitionItem, ( options.forceTransition || cacheType == .none) else { - indicator?.kf_stopAnimating() + maybeIndicator?.stopAnimatingView() sSelf.image = image completionHandler?(image, error, cacheType, imageURL) return @@ -116,7 +109,7 @@ extension ImageView { #if !os(macOS) UIView.transition(with: sSelf, duration: 0.0, options: [], - animations: { indicator?.kf_stopAnimating() }, + animations: { maybeIndicator?.stopAnimatingView() }, completion: { _ in UIView.transition(with: sSelf, duration: transition.duration, options: [transition.animationOptions, .allowUserInteraction], @@ -156,10 +149,26 @@ extension ImageView { } } +/** + Enum for the types of indicators that the user can choose from. + */ +extension ImageView { + public enum IndicatorType { + /// No indicator. + case none + /// Use system activity indicator. + case activity + /// Use an image as indicator. GIF is supported. + case image(imageData: Data) + /// Use a custom indicator, which conforms to the `Indicator` protocol. + case custom(indicator: Indicator) + } +} + // MARK: - Associated Object private var lastURLKey: Void? private var indicatorKey: Void? -private var showIndicatorWhenLoadingKey: Void? +private var indicatorTypeKey: Void? private var imageTaskKey: Void? extension ImageView { @@ -172,61 +181,55 @@ extension ImageView { objc_setAssociatedObject(self, &lastURLKey, url, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } - /// Whether show an animating indicator when the image view is loading an image or not. - /// Default is false. - public var kf_showIndicatorWhenLoading: Bool { + /// Holds which indicator type is going to be used. + /// Default is .None + public var kf_indicatorType: IndicatorType { get { - if let result = objc_getAssociatedObject(self, &showIndicatorWhenLoadingKey) as? NSNumber { - return result.boolValue - } else { - return false - } + let indicator = (objc_getAssociatedObject(self, &indicatorTypeKey) as? Box)?.value + return indicator ?? .none } set { - if kf_showIndicatorWhenLoading == newValue { - return - } else { - if newValue { - -#if os(macOS) - let indicator = NSProgressIndicator(frame: CGRect(x: 0, y: 0, width: 16, height: 16)) - indicator.controlSize = .small - indicator.style = .spinningStyle -#else - #if os(tvOS) - let indicatorStyle = UIActivityIndicatorViewStyle.white - #else - let indicatorStyle = UIActivityIndicatorViewStyle.gray - #endif - let indicator = UIActivityIndicatorView(activityIndicatorStyle:indicatorStyle) - indicator.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleBottomMargin, .flexibleTopMargin] -#endif - - indicator.kf_center = CGPoint(x: bounds.midX, y: bounds.midY) - indicator.isHidden = true - - self.addSubview(indicator) - - kf_setIndicator(indicator) - } else { - kf_indicator?.removeFromSuperview() - kf_setIndicator(nil) - } - - objc_setAssociatedObject(self, &showIndicatorWhenLoadingKey, NSNumber(value: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + switch newValue { + case .none: + kf_indicator = nil + case .activity: + kf_indicator = ActivityIndicator() + case .image(let data): + kf_indicator = ImageIndicator(imageData: data) + case .custom(let indicator): + kf_indicator = indicator } + + objc_setAssociatedObject(self, &indicatorTypeKey, Box(value: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } - /// The indicator view showing when loading. This will be `nil` if `kf_showIndicatorWhenLoading` is false. - /// You may want to use this to set the indicator style or color when you set `kf_showIndicatorWhenLoading` to true. - public var kf_indicator: IndicatorView? { - return objc_getAssociatedObject(self, &indicatorKey) as? IndicatorView - } - - fileprivate func kf_setIndicator(_ indicator: IndicatorView?) { - objc_setAssociatedObject(self, &indicatorKey, indicator, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + /// `kf_indicator` holds any type that conforms to the protocol `Indicator`. + /// The protocol `Indicator` has a `view` property that will be shown when loading an image. + /// Everything will be `nil` if `kf_indicatorType` is .None. + public private(set) var kf_indicator: Indicator? { + get { + return (objc_getAssociatedObject(self, &indicatorKey) as? Box)?.value + } + + set { + // Remove previous + if let previousIndicator = kf_indicator { + previousIndicator.view.removeFromSuperview() + } + + // Add new + if var newIndicator = newValue { + newIndicator.view.frame = self.frame + newIndicator.viewCenter = CGPoint(x: bounds.midX, y: bounds.midY) + newIndicator.view.isHidden = true + self.addSubview(newIndicator.view) + } + + // Save in associated object + objc_setAssociatedObject(self, &indicatorKey, Box(value: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } } fileprivate var kf_imageTask: RetrieveImageTask? { @@ -237,45 +240,3 @@ extension ImageView { objc_setAssociatedObject(self, &imageTaskKey, task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } - - -extension IndicatorView { - func kf_startAnimating() { - #if os(macOS) - startAnimation(nil) - #else - startAnimating() - #endif - isHidden = false - } - - func kf_stopAnimating() { - #if os(macOS) - stopAnimation(nil) - #else - stopAnimating() - #endif - isHidden = true - } - - #if os(macOS) - var kf_center: CGPoint { - get { - return CGPoint(x: frame.origin.x + frame.size.width / 2.0, y: frame.origin.y + frame.size.height / 2.0 ) - } - set { - let newFrame = CGRect(x: newValue.x - frame.size.width / 2.0, y: newValue.y - frame.size.height / 2.0, width: frame.size.width, height: frame.size.height) - frame = newFrame - } - } - #else - var kf_center: CGPoint { - get { - return center - } - set { - center = newValue - } - } - #endif -} diff --git a/Sources/Indicator.swift b/Sources/Indicator.swift index 2d05e9659..bf276c452 100644 --- a/Sources/Indicator.swift +++ b/Sources/Indicator.swift @@ -93,7 +93,7 @@ struct ActivityIndicator: Indicator { #else activityIndicatorView.startAnimating() #endif - activityIndicatorView.hidden = false + activityIndicatorView.isHidden = false } func stopAnimatingView() { @@ -102,27 +102,22 @@ struct ActivityIndicator: Indicator { #else activityIndicatorView.stopAnimating() #endif - activityIndicatorView.hidden = true + activityIndicatorView.isHidden = true } init() { #if os(OSX) activityIndicatorView = NSProgressIndicator(frame: CGRect(x: 0, y: 0, width: 16, height: 16)) - - #if swift(>=2.3) - activityIndicatorView.controlSize = .Small - #else - activityIndicatorView.controlSize = .SmallControlSize - #endif - activityIndicatorView.style = .SpinningStyle + activityIndicatorView.controlSize = .small + activityIndicatorView.style = .spinningStyle #else #if os(tvOS) - let indicatorStyle = UIActivityIndicatorViewStyle.White + let indicatorStyle = UIActivityIndicatorViewStyle.white #else - let indicatorStyle = UIActivityIndicatorViewStyle.Gray + let indicatorStyle = UIActivityIndicatorViewStyle.gray #endif activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle:indicatorStyle) - activityIndicatorView.autoresizingMask = [.FlexibleLeftMargin, .FlexibleRightMargin, .FlexibleBottomMargin, .FlexibleTopMargin] + activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleBottomMargin, .flexibleTopMargin] #endif } } @@ -136,23 +131,32 @@ struct ImageIndicator: Indicator { return animatedImageIndicatorView } - init(imageData data: NSData) { + init?(imageData data: Data, processor: ImageProcessor = DefaultImageProcessor.default, options: KingfisherOptionsInfo = KingfisherEmptyOptionsInfo) { + + var options = options + // Use normal image view to show gif, so we need to preload all gif data. + if !options.preloadAllGIFData { + options.append(.preloadAllGIFData) + } + + guard let image = processor.process(item: .data(data), options: options) else { + return nil + } - let image = Image.kf_imageWithData(data, scale: 1.0, preloadAllGIFData: true) animatedImageIndicatorView = ImageView() animatedImageIndicatorView.image = image #if os(OSX) // Need for gif to animate on OSX - self.animatedImageIndicatorView.imageScaling = .ScaleNone + self.animatedImageIndicatorView.imageScaling = .scaleNone self.animatedImageIndicatorView.canDrawSubviewsIntoLayer = true #else - animatedImageIndicatorView.contentMode = .Center + animatedImageIndicatorView.contentMode = .center - animatedImageIndicatorView.autoresizingMask = [.FlexibleLeftMargin, - .FlexibleRightMargin, - .FlexibleBottomMargin, - .FlexibleTopMargin] + animatedImageIndicatorView.autoresizingMask = [.flexibleLeftMargin, + .flexibleRightMargin, + .flexibleBottomMargin, + .flexibleTopMargin] #endif } @@ -162,7 +166,7 @@ struct ImageIndicator: Indicator { #else animatedImageIndicatorView.startAnimating() #endif - animatedImageIndicatorView.hidden = false + animatedImageIndicatorView.isHidden = false } func stopAnimatingView() { @@ -171,6 +175,6 @@ struct ImageIndicator: Indicator { #else animatedImageIndicatorView.stopAnimating() #endif - animatedImageIndicatorView.hidden = true + animatedImageIndicatorView.isHidden = true } }