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

Merge pull request #756 from onevcat/teameh-custom-placeholder

Teameh custom placeholder
Wei Wang 8 лет назад
Родитель
Сommit
61afe3a292

+ 35 - 0
Demo/Kingfisher-Demo/MyCustomPlaceholder.swift

@@ -0,0 +1,35 @@
+//
+//  MyCustomPlaceholder.swift
+//  Kingfisher
+//
+//  Created by Tieme van Veen on 28/08/2017.
+//  Copyright (c) 2017 Wei Wang <onevcat@gmail.com>
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+
+import UIKit
+import Kingfisher
+
+class MyCustomPlaceholder : UIView, Placeholder {
+    override func draw(_ rect: CGRect) {
+        let path = UIBezierPath(ovalIn: rect)
+        UIColor.red.setFill()
+        path.fill()
+    }
+}

+ 5 - 2
Demo/Kingfisher-Demo/ViewController.swift

@@ -67,9 +67,12 @@ extension ViewController {
     override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
         
         let url = URL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher/master/images/kingfisher-\(indexPath.row + 1).jpg")!
-        
+
+        // Bottom half has a custom placeholder, tap reload to see it
+        let placeholder = indexPath.row > 5 ? MyCustomPlaceholder() : nil
+
         _ = (cell as! CollectionViewCell).cellImageView.kf.setImage(with: url,
-                                           placeholder: nil,
+                                           placeholder: placeholder,
                                            options: [.transition(ImageTransition.fade(1))],
                                            progressBlock: { receivedSize, totalSize in
                                             print("\(indexPath.row + 1): \(receivedSize)/\(totalSize)")

+ 14 - 0
Kingfisher.xcodeproj/project.pbxproj

@@ -10,6 +10,11 @@
 		008050DF31B6F246FFC8BEA1 /* libPods-KingfisherTests-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1446B878F6627F0B79BB366C /* libPods-KingfisherTests-tvOS.a */; };
 		182FFF781CC9ACBA004B728D /* NSButton+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182FFF771CC9ACBA004B728D /* NSButton+Kingfisher.swift */; };
 		185218B61CC07F8300BD58DE /* NSButtonExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 185218B51CC07F8300BD58DE /* NSButtonExtensionTests.swift */; };
+		444C03E91F548F6100990BCC /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444C03E01F547AAE00990BCC /* Placeholder.swift */; };
+		444C03EA1F548F6200990BCC /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444C03E01F547AAE00990BCC /* Placeholder.swift */; };
+		444C03EB1F548F6200990BCC /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444C03E01F547AAE00990BCC /* Placeholder.swift */; };
+		444C03ED1F548F8600990BCC /* MyCustomPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444C03E41F548DA000990BCC /* MyCustomPlaceholder.swift */; };
+		444C03EE1F548F8700990BCC /* MyCustomPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444C03E41F548DA000990BCC /* MyCustomPlaceholder.swift */; };
 		4B164AD01B8D556900768EC6 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B164ACE1B8D554200768EC6 /* CFNetwork.framework */; };
 		4B2944641C3D03980088C3E7 /* Kingfisher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B2944481C3D01B20088C3E7 /* Kingfisher.framework */; };
 		4B2B8E4A1D70128200FC4749 /* ImageProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B8E491D70128200FC4749 /* ImageProcessor.swift */; };
@@ -572,6 +577,8 @@
 		1446B878F6627F0B79BB366C /* libPods-KingfisherTests-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-KingfisherTests-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		182FFF771CC9ACBA004B728D /* NSButton+Kingfisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSButton+Kingfisher.swift"; path = "Sources/NSButton+Kingfisher.swift"; sourceTree = "<group>"; };
 		185218B51CC07F8300BD58DE /* NSButtonExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSButtonExtensionTests.swift; sourceTree = "<group>"; };
+		444C03E01F547AAE00990BCC /* Placeholder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Placeholder.swift; path = Sources/Placeholder.swift; sourceTree = "<group>"; };
+		444C03E41F548DA000990BCC /* MyCustomPlaceholder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyCustomPlaceholder.swift; sourceTree = "<group>"; };
 		4AB2628A580157ADADCE0011 /* Pods-KingfisherTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KingfisherTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-KingfisherTests/Pods-KingfisherTests.debug.xcconfig"; sourceTree = "<group>"; };
 		4B164ACE1B8D554200768EC6 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
 		4B2944481C3D01B20088C3E7 /* Kingfisher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1069,6 +1076,7 @@
 				D10945F11C526B6C001408EB /* KingfisherManager.swift */,
 				D10945F21C526B6C001408EB /* KingfisherOptionsInfo.swift */,
 				4B6313F31D766BEF0078E017 /* Filter.swift */,
+				444C03E01F547AAE00990BCC /* Placeholder.swift */,
 				D10945F31C526B6C001408EB /* Resource.swift */,
 				4BD8E04B1D9237E200A091BE /* Kingfisher.swift */,
 			);
@@ -1196,6 +1204,7 @@
 				D12E0C921C47F91800AC98AD /* Images.xcassets */,
 				D12E0C931C47F91800AC98AD /* Info.plist */,
 				D12E0C941C47F91800AC98AD /* ViewController.swift */,
+				444C03E41F548DA000990BCC /* MyCustomPlaceholder.swift */,
 			);
 			name = "Kingfisher-Demo";
 			path = "Demo/Kingfisher-Demo";
@@ -2334,6 +2343,7 @@
 				4BD8E04E1D9237E200A091BE /* Kingfisher.swift in Sources */,
 				D10946221C526C61001408EB /* String+MD5.swift in Sources */,
 				D10946231C526C61001408EB /* ThreadHelper.swift in Sources */,
+				444C03EB1F548F6200990BCC /* Placeholder.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -2386,6 +2396,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				444C03EE1F548F8700990BCC /* MyCustomPlaceholder.swift in Sources */,
 				D12E0CB51C47F9C100AC98AD /* CollectionViewCell.swift in Sources */,
 				D12E0CB61C47F9C100AC98AD /* ViewController.swift in Sources */,
 				D12E0CA21C47F92200AC98AD /* AppDelegate.swift in Sources */,
@@ -2415,6 +2426,7 @@
 				D10946161C526C0D001408EB /* String+MD5.swift in Sources */,
 				4BB24C3E1D79215A00CD5F9C /* CacheSerializer.swift in Sources */,
 				D10946171C526C0D001408EB /* ThreadHelper.swift in Sources */,
+				444C03EA1F548F6200990BCC /* Placeholder.swift in Sources */,
 				FB402D0F1EDEAB7E002B62A1 /* FormatIndicatedCacheSerializer.swift in Sources */,
 				D10946181C526C0D001408EB /* UIButton+Kingfisher.swift in Sources */,
 			);
@@ -2455,6 +2467,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				444C03ED1F548F8600990BCC /* MyCustomPlaceholder.swift in Sources */,
 				D12E0C9B1C47F91800AC98AD /* ViewController.swift in Sources */,
 				D12E0C981C47F91800AC98AD /* CollectionViewCell.swift in Sources */,
 				D12E0C951C47F91800AC98AD /* AppDelegate.swift in Sources */,
@@ -2484,6 +2497,7 @@
 				D10945FF1C526B86001408EB /* String+MD5.swift in Sources */,
 				4BB24C3D1D79215A00CD5F9C /* CacheSerializer.swift in Sources */,
 				D10946001C526B86001408EB /* ThreadHelper.swift in Sources */,
+				444C03E91F548F6100990BCC /* Placeholder.swift in Sources */,
 				FB402D0E1EDEAB7E002B62A1 /* FormatIndicatedCacheSerializer.swift in Sources */,
 				D10946011C526B86001408EB /* UIButton+Kingfisher.swift in Sources */,
 			);

+ 30 - 5
Sources/ImageView+Kingfisher.swift

@@ -55,22 +55,23 @@ extension Kingfisher where Base: ImageView {
      */
     @discardableResult
     public func setImage(with resource: Resource?,
-                         placeholder: Image? = nil,
+                         placeholder: Placeholder? = nil,
                          options: KingfisherOptionsInfo? = nil,
                          progressBlock: DownloadProgressBlock? = nil,
                          completionHandler: CompletionHandler? = nil) -> RetrieveImageTask
     {
         guard let resource = resource else {
-            base.image = placeholder
+            self.placeholder = placeholder
             setWebURL(nil)
             completionHandler?(nil, nil, .none, nil)
             return .empty
         }
         
         var options = KingfisherManager.shared.defaultOptions + (options ?? KingfisherEmptyOptionsInfo)
+        let noImageOrPlaceholderSet = base.image == nil && self.placeholder == nil
         
-        if !options.keepCurrentImageWhileLoading || base.image == nil {
-            base.image = placeholder
+        if !options.keepCurrentImageWhileLoading || noImageOrPlaceholderSet { // Always set placeholder while there is no image/placehoer yet.
+            self.placeholder = placeholder
         }
 
         let maybeIndicator = indicator
@@ -110,6 +111,7 @@ extension Kingfisher where Base: ImageView {
                     guard let transitionItem = options.lastMatchIgnoringAssociatedValue(.transition(.none)),
                         case .transition(let transition) = transitionItem, ( options.forceTransition || cacheType == .none) else
                     {
+                        self.placeholder = nil
                         strongBase.image = image
                         completionHandler?(image, error, cacheType, imageURL)
                         return
@@ -119,6 +121,8 @@ extension Kingfisher where Base: ImageView {
                         UIView.transition(with: strongBase, duration: 0.0, options: [],
                                           animations: { maybeIndicator?.stopAnimatingView() },
                                           completion: { _ in
+
+                                            self.placeholder = nil
                                             UIView.transition(with: strongBase, duration: transition.duration,
                                                               options: [transition.animationOptions, .allowUserInteraction],
                                                               animations: {
@@ -152,6 +156,7 @@ extension Kingfisher where Base: ImageView {
 private var lastURLKey: Void?
 private var indicatorKey: Void?
 private var indicatorTypeKey: Void?
+private var placeholderKey: Void?
 private var imageTaskKey: Void?
 
 extension Kingfisher where Base: ImageView {
@@ -225,6 +230,26 @@ extension Kingfisher where Base: ImageView {
     fileprivate func setImageTask(_ task: RetrieveImageTask?) {
         objc_setAssociatedObject(base, &imageTaskKey, task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
     }
+    
+    public fileprivate(set) var placeholder: Placeholder? {
+        get {
+            return (objc_getAssociatedObject(base, &placeholderKey) as? Box<Placeholder?>)?.value
+        }
+        
+        set {
+            if let previousPlaceholder = placeholder {
+                previousPlaceholder.remove(from: base)
+            }
+            
+            if let newPlaceholder = newValue {
+                newPlaceholder.add(to: base)
+            } else {
+                base.image = nil
+            }
+            
+            objc_setAssociatedObject(base, &placeholderKey, Box(value: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+        }
+    }
 }
 
 
@@ -250,7 +275,7 @@ extension ImageView {
     @available(*, deprecated, message: "Extensions directly on image views are deprecated. Use `imageView.kf.setImage` instead.", renamed: "kf.setImage")
     @discardableResult
     public func kf_setImage(with resource: Resource?,
-                              placeholder: Image? = nil,
+                              placeholder: Placeholder? = nil,
                                   options: KingfisherOptionsInfo? = nil,
                             progressBlock: DownloadProgressBlock? = nil,
                         completionHandler: CompletionHandler? = nil) -> RetrieveImageTask

+ 2 - 0
Sources/Kingfisher.swift

@@ -30,12 +30,14 @@ import ImageIO
 #if os(macOS)
     import AppKit
     public typealias Image = NSImage
+    public typealias View = NSView
     public typealias Color = NSColor
     public typealias ImageView = NSImageView
     typealias Button = NSButton
 #else
     import UIKit
     public typealias Image = UIImage
+    public typealias View = UIView
     public typealias Color = UIColor
     #if !os(watchOS)
     public typealias ImageView = UIImageView

+ 61 - 0
Sources/Placeholder.swift

@@ -0,0 +1,61 @@
+//
+//  Placeholder.swift
+//  Kingfisher
+//
+//  Created by Tieme van Veen on 28/08/2017.
+//
+//  Copyright (c) 2017 Wei Wang <onevcat@gmail.com>
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+
+#if os(macOS)
+    import AppKit
+#else
+    import UIKit
+#endif
+
+public protocol Placeholder {
+    func add(to imageView: ImageView)
+    func remove(from imageView: ImageView)
+}
+
+extension Placeholder where Self: Image {
+    public func add(to imageView: ImageView) { imageView.image = self }
+    public func remove(from imageView: ImageView) { imageView.image = nil }
+}
+
+extension Image: Placeholder {}
+
+extension Placeholder where Self: View {
+    public func add(to imageView: ImageView) {
+        imageView.addSubview(self)
+
+        self.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: imageView, attribute: .centerX, multiplier: 1, constant: 0),
+            NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: imageView, attribute: .centerY, multiplier: 1, constant: 0),
+            NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: imageView, attribute: .height, multiplier: 1, constant: 0),
+            NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: imageView, attribute: .width, multiplier: 1, constant: 0)
+            ])
+    }
+
+    public func remove(from imageView: ImageView) {
+        self.removeFromSuperview()
+    }
+}