added iOS source code
[wl-app.git] / iOS / Pods / Kingfisher / Sources / ImageView+Kingfisher.swift
1 //
2 //  ImageView+Kingfisher.swift
3 //  Kingfisher
4 //
5 //  Created by Wei Wang on 15/4/6.
6 //
7 //  Copyright (c) 2018 Wei Wang <onevcat@gmail.com>
8 //
9 //  Permission is hereby granted, free of charge, to any person obtaining a copy
10 //  of this software and associated documentation files (the "Software"), to deal
11 //  in the Software without restriction, including without limitation the rights
12 //  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 //  copies of the Software, and to permit persons to whom the Software is
14 //  furnished to do so, subject to the following conditions:
15 //
16 //  The above copyright notice and this permission notice shall be included in
17 //  all copies or substantial portions of the Software.
18 //
19 //  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 //  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 //  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 //  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 //  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 //  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 //  THE SOFTWARE.
26
27
28 #if os(macOS)
29 import AppKit
30 #else
31 import UIKit
32 #endif
33
34 // MARK: - Extension methods.
35 /**
36  *    Set image to use from web.
37  */
38 extension Kingfisher where Base: ImageView {
39     /**
40      Set an image with a resource, a placeholder image, options, progress handler and completion handler.
41      
42      - parameter resource:          Resource object contains information such as `cacheKey` and `downloadURL`.
43      - parameter placeholder:       A placeholder image when retrieving the image at URL.
44      - parameter options:           A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
45      - parameter progressBlock:     Called when the image downloading progress gets updated.
46      - parameter completionHandler: Called when the image retrieved and set.
47      
48      - returns: A task represents the retrieving process.
49      
50      - note: Both the `progressBlock` and `completionHandler` will be invoked in main thread.
51      The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
52      
53      If `resource` is `nil`, the `placeholder` image will be set and
54      `completionHandler` will be called with both `error` and `image` being `nil`.
55      */
56     @discardableResult
57     public func setImage(with resource: Resource?,
58                          placeholder: Placeholder? = nil,
59                          options: KingfisherOptionsInfo? = nil,
60                          progressBlock: DownloadProgressBlock? = nil,
61                          completionHandler: CompletionHandler? = nil) -> RetrieveImageTask
62     {
63         guard let resource = resource else {
64             self.placeholder = placeholder
65             setWebURL(nil)
66             completionHandler?(nil, nil, .none, nil)
67             return .empty
68         }
69         
70         var options = KingfisherManager.shared.defaultOptions + (options ?? KingfisherEmptyOptionsInfo)
71         let noImageOrPlaceholderSet = base.image == nil && self.placeholder == nil
72         
73         if !options.keepCurrentImageWhileLoading || noImageOrPlaceholderSet { // Always set placeholder while there is no image/placehoer yet.
74             self.placeholder = placeholder
75         }
76
77         let maybeIndicator = indicator
78         maybeIndicator?.startAnimatingView()
79         
80         setWebURL(resource.downloadURL)
81
82         if base.shouldPreloadAllAnimation() {
83             options.append(.preloadAllAnimationData)
84         }
85         
86         let task = KingfisherManager.shared.retrieveImage(
87             with: resource,
88             options: options,
89             progressBlock: { receivedSize, totalSize in
90                 guard resource.downloadURL == self.webURL else {
91                     return
92                 }
93                 if let progressBlock = progressBlock {
94                     progressBlock(receivedSize, totalSize)
95                 }
96             },
97             completionHandler: {[weak base] image, error, cacheType, imageURL in
98                 DispatchQueue.main.safeAsync {
99                     maybeIndicator?.stopAnimatingView()
100                     guard let strongBase = base, imageURL == self.webURL else {
101                         completionHandler?(image, error, cacheType, imageURL)
102                         return
103                     }
104                     
105                     self.setImageTask(nil)
106                     guard let image = image else {
107                         completionHandler?(nil, error, cacheType, imageURL)
108                         return
109                     }
110                     
111                     guard let transitionItem = options.lastMatchIgnoringAssociatedValue(.transition(.none)),
112                         case .transition(let transition) = transitionItem, ( options.forceTransition || cacheType == .none) else
113                     {
114                         self.placeholder = nil
115                         strongBase.image = image
116                         completionHandler?(image, error, cacheType, imageURL)
117                         return
118                     }
119                     
120                     #if !os(macOS)
121                         UIView.transition(with: strongBase, duration: 0.0, options: [],
122                                           animations: { maybeIndicator?.stopAnimatingView() },
123                                           completion: { _ in
124
125                                             self.placeholder = nil
126                                             UIView.transition(with: strongBase, duration: transition.duration,
127                                                               options: [transition.animationOptions, .allowUserInteraction],
128                                                               animations: {
129                                                                 // Set image property in the animation.
130                                                                 transition.animations?(strongBase, image)
131                                                               },
132                                                               completion: { finished in
133                                                                 transition.completion?(finished)
134                                                                 completionHandler?(image, error, cacheType, imageURL)
135                                                               })
136                                           })
137                     #endif
138                 }
139             })
140         
141         setImageTask(task)
142         
143         return task
144     }
145     
146     /**
147      Cancel the image download task bounded to the image view if it is running.
148      Nothing will happen if the downloading has already finished.
149      */
150     public func cancelDownloadTask() {
151         imageTask?.cancel()
152     }
153 }
154
155 // MARK: - Associated Object
156 private var lastURLKey: Void?
157 private var indicatorKey: Void?
158 private var indicatorTypeKey: Void?
159 private var placeholderKey: Void?
160 private var imageTaskKey: Void?
161
162 extension Kingfisher where Base: ImageView {
163     /// Get the image URL binded to this image view.
164     public var webURL: URL? {
165         return objc_getAssociatedObject(base, &lastURLKey) as? URL
166     }
167     
168     fileprivate func setWebURL(_ url: URL?) {
169         objc_setAssociatedObject(base, &lastURLKey, url, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
170     }
171     
172     /// Holds which indicator type is going to be used.
173     /// Default is .none, means no indicator will be shown.
174     public var indicatorType: IndicatorType {
175         get {
176             let indicator = objc_getAssociatedObject(base, &indicatorTypeKey) as? IndicatorType
177             return indicator ?? .none
178         }
179         
180         set {
181             switch newValue {
182             case .none:
183                 indicator = nil
184             case .activity:
185                 indicator = ActivityIndicator()
186             case .image(let data):
187                 indicator = ImageIndicator(imageData: data)
188             case .custom(let anIndicator):
189                 indicator = anIndicator
190             }
191             
192             objc_setAssociatedObject(base, &indicatorTypeKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
193         }
194     }
195     
196     /// Holds any type that conforms to the protocol `Indicator`.
197     /// The protocol `Indicator` has a `view` property that will be shown when loading an image.
198     /// It will be `nil` if `indicatorType` is `.none`.
199     public fileprivate(set) var indicator: Indicator? {
200         get {
201             guard let box = objc_getAssociatedObject(base, &indicatorKey) as? Box<Indicator> else {
202                 return nil
203             }
204             return box.value
205         }
206         
207         set {
208             // Remove previous
209             if let previousIndicator = indicator {
210                 previousIndicator.view.removeFromSuperview()
211             }
212             
213             // Add new
214             if var newIndicator = newValue {
215                 // Set default indicator frame if the view's frame not set.
216                 if newIndicator.view.frame == .zero {
217                     newIndicator.view.frame = base.frame
218                 }
219                 newIndicator.viewCenter = CGPoint(x: base.bounds.midX, y: base.bounds.midY)
220                 newIndicator.view.isHidden = true
221                 base.addSubview(newIndicator.view)
222             }
223             
224             // Save in associated object
225             // Wrap newValue with Box to workaround an issue that Swift does not recognize
226             // and casting protocol for associate object correctly. https://github.com/onevcat/Kingfisher/issues/872
227             objc_setAssociatedObject(base, &indicatorKey, newValue.map(Box.init), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
228         }
229     }
230     
231     fileprivate var imageTask: RetrieveImageTask? {
232         return objc_getAssociatedObject(base, &imageTaskKey) as? RetrieveImageTask
233     }
234     
235     fileprivate func setImageTask(_ task: RetrieveImageTask?) {
236         objc_setAssociatedObject(base, &imageTaskKey, task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
237     }
238     
239     public fileprivate(set) var placeholder: Placeholder? {
240         get {
241             return objc_getAssociatedObject(base, &placeholderKey) as? Placeholder
242         }
243         
244         set {
245             if let previousPlaceholder = placeholder {
246                 previousPlaceholder.remove(from: base)
247             }
248             
249             if let newPlaceholder = newValue {
250                 newPlaceholder.add(to: base)
251             } else {
252                 base.image = nil
253             }
254             
255             objc_setAssociatedObject(base, &placeholderKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
256         }
257     }
258 }
259
260
261 @objc extension ImageView {
262     func shouldPreloadAllAnimation() -> Bool { return true }
263 }