ImageView+Kingfisher.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. //
  2. // ImageView+Kingfisher.swift
  3. // Kingfisher
  4. //
  5. // Created by Wei Wang on 15/4/6.
  6. //
  7. // Copyright (c) 2016 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. #if os(OSX)
  27. import AppKit
  28. typealias ImageView = NSImageView
  29. public typealias IndicatorView = NSProgressIndicator
  30. #else
  31. import UIKit
  32. typealias ImageView = UIImageView
  33. public typealias IndicatorView = UIActivityIndicatorView
  34. #endif
  35. // MARK: - Set Images
  36. /**
  37. * Set image to use from web.
  38. */
  39. extension ImageView {
  40. /**
  41. Set an image with a resource.
  42. It will ask for Kingfisher's manager to get the image for the `cacheKey` property in `resource`.
  43. The memory and disk will be searched first. If the manager does not find it, it will try to download the image at the `resource.downloadURL` and store it with `resource.cacheKey` for next use.
  44. - parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`.
  45. - returns: A task represents the retrieving process.
  46. */
  47. public func kf_setImageWithResource(resource: Resource) -> RetrieveImageTask
  48. {
  49. return kf_setImageWithResource(resource, placeholderImage: nil, optionsInfo: nil, progressBlock: nil, completionHandler: nil)
  50. }
  51. /**
  52. Set an image with a URL.
  53. It will ask for Kingfisher's manager to get the image for the URL.
  54. The memory and disk will be searched first with `URL.absoluteString` as the cache key. If the manager does not find it, it will try to download the image at this URL and store the image with `URL.absoluteString` as cache key for next use.
  55. If you need to specify the key other than `URL.absoluteString`, please use resource version of these APIs with `resource.cacheKey` set to what you want.
  56. - parameter URL: The URL of image.
  57. - returns: A task represents the retrieving process.
  58. */
  59. public func kf_setImageWithURL(URL: NSURL) -> RetrieveImageTask
  60. {
  61. return kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: nil, progressBlock: nil, completionHandler: nil)
  62. }
  63. /**
  64. Set an image with a resource and a placeholder image.
  65. - parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`.
  66. - parameter placeholderImage: A placeholder image when retrieving the image at URL.
  67. - returns: A task represents the retrieving process.
  68. */
  69. public func kf_setImageWithResource(resource: Resource,
  70. placeholderImage: Image?) -> RetrieveImageTask
  71. {
  72. return kf_setImageWithResource(resource, placeholderImage: placeholderImage, optionsInfo: nil, progressBlock: nil, completionHandler: nil)
  73. }
  74. /**
  75. Set an image with a URL and a placeholder image.
  76. - parameter URL: The URL of image.
  77. - parameter placeholderImage: A placeholder image when retrieving the image at URL.
  78. - returns: A task represents the retrieving process.
  79. */
  80. public func kf_setImageWithURL(URL: NSURL,
  81. placeholderImage: Image?) -> RetrieveImageTask
  82. {
  83. return kf_setImageWithURL(URL, placeholderImage: placeholderImage, optionsInfo: nil, progressBlock: nil, completionHandler: nil)
  84. }
  85. /**
  86. Set an image with a resource, a placaholder image and options.
  87. - parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`.
  88. - parameter placeholderImage: A placeholder image when retrieving the image at URL.
  89. - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
  90. - returns: A task represents the retrieving process.
  91. */
  92. public func kf_setImageWithResource(resource: Resource,
  93. placeholderImage: Image?,
  94. optionsInfo: KingfisherOptionsInfo?) -> RetrieveImageTask
  95. {
  96. return kf_setImageWithResource(resource, placeholderImage: placeholderImage, optionsInfo: optionsInfo, progressBlock: nil, completionHandler: nil)
  97. }
  98. /**
  99. Set an image with a URL, a placaholder image and options.
  100. - parameter URL: The URL of image.
  101. - parameter placeholderImage: A placeholder image when retrieving the image at URL.
  102. - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
  103. - returns: A task represents the retrieving process.
  104. */
  105. public func kf_setImageWithURL(URL: NSURL,
  106. placeholderImage: Image?,
  107. optionsInfo: KingfisherOptionsInfo?) -> RetrieveImageTask
  108. {
  109. return kf_setImageWithURL(URL, placeholderImage: placeholderImage, optionsInfo: optionsInfo, progressBlock: nil, completionHandler: nil)
  110. }
  111. /**
  112. Set an image with a resource, a placeholder image, options and completion handler.
  113. - parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`.
  114. - parameter placeholderImage: A placeholder image when retrieving the image at URL.
  115. - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
  116. - parameter completionHandler: Called when the image retrieved and set.
  117. - returns: A task represents the retrieving process.
  118. - note: `completionHandler` will be invoked in main thread.
  119. The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
  120. */
  121. public func kf_setImageWithResource(resource: Resource,
  122. placeholderImage: Image?,
  123. optionsInfo: KingfisherOptionsInfo?,
  124. completionHandler: CompletionHandler?) -> RetrieveImageTask
  125. {
  126. return kf_setImageWithResource(resource, placeholderImage: placeholderImage, optionsInfo: optionsInfo, progressBlock: nil, completionHandler: completionHandler)
  127. }
  128. /**
  129. Set an image with a URL, a placeholder image, options and completion handler.
  130. - parameter URL: The URL of image.
  131. - parameter placeholderImage: A placeholder image when retrieving the image at URL.
  132. - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
  133. - parameter completionHandler: Called when the image retrieved and set.
  134. - returns: A task represents the retrieving process.
  135. - note: `completionHandler` will be invoked in main thread.
  136. The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
  137. */
  138. public func kf_setImageWithURL(URL: NSURL,
  139. placeholderImage: Image?,
  140. optionsInfo: KingfisherOptionsInfo?,
  141. completionHandler: CompletionHandler?) -> RetrieveImageTask
  142. {
  143. return kf_setImageWithURL(URL, placeholderImage: placeholderImage, optionsInfo: optionsInfo, progressBlock: nil, completionHandler: completionHandler)
  144. }
  145. /**
  146. Set an image with a URL, a placeholder image, options, progress handler and completion handler.
  147. - parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`.
  148. - parameter placeholderImage: A placeholder image when retrieving the image at URL.
  149. - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
  150. - parameter progressBlock: Called when the image downloading progress gets updated.
  151. - parameter completionHandler: Called when the image retrieved and set.
  152. - returns: A task represents the retrieving process.
  153. - note: Both the `progressBlock` and `completionHandler` will be invoked in main thread.
  154. The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
  155. */
  156. public func kf_setImageWithResource(resource: Resource,
  157. placeholderImage: Image?,
  158. optionsInfo: KingfisherOptionsInfo?,
  159. progressBlock: DownloadProgressBlock?,
  160. completionHandler: CompletionHandler?) -> RetrieveImageTask
  161. {
  162. let showIndicatorWhenLoading = kf_showIndicatorWhenLoading
  163. var indicator: IndicatorView? = nil
  164. if showIndicatorWhenLoading {
  165. indicator = kf_indicator
  166. indicator?.hidden = false
  167. indicator?.kf_startAnimating()
  168. }
  169. image = placeholderImage
  170. kf_setWebURL(resource.downloadURL)
  171. let task = KingfisherManager.sharedManager.retrieveImageWithResource(resource, optionsInfo: optionsInfo,
  172. progressBlock: { receivedSize, totalSize in
  173. if let progressBlock = progressBlock {
  174. progressBlock(receivedSize: receivedSize, totalSize: totalSize)
  175. }
  176. },
  177. completionHandler: {[weak self] image, error, cacheType, imageURL in
  178. dispatch_async_safely_to_main_queue {
  179. guard let sSelf = self where imageURL == sSelf.kf_webURL else {
  180. completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
  181. return
  182. }
  183. sSelf.kf_setImageTask(nil)
  184. guard let image = image else {
  185. indicator?.kf_stopAnimating()
  186. completionHandler?(image: nil, error: error, cacheType: cacheType, imageURL: imageURL)
  187. return
  188. }
  189. if let transitionItem = optionsInfo?.kf_firstMatchIgnoringAssociatedValue(.Transition(.None)),
  190. case .Transition(let transition) = transitionItem where cacheType == .None {
  191. #if !os(OSX)
  192. UIView.transitionWithView(sSelf, duration: 0.0, options: [],
  193. animations: {
  194. indicator?.kf_stopAnimating()
  195. },
  196. completion: { finished in
  197. UIView.transitionWithView(sSelf, duration: transition.duration,
  198. options: [transition.animationOptions, .AllowUserInteraction],
  199. animations: {
  200. transition.animations?(sSelf, image)
  201. },
  202. completion: { finished in
  203. transition.completion?(finished)
  204. completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
  205. })
  206. })
  207. #endif
  208. } else {
  209. indicator?.kf_stopAnimating()
  210. sSelf.image = image
  211. completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
  212. }
  213. }
  214. })
  215. kf_setImageTask(task)
  216. return task
  217. }
  218. /**
  219. Set an image with a URL, a placeholder image, options, progress handler and completion handler.
  220. - parameter URL: The URL of image.
  221. - parameter placeholderImage: A placeholder image when retrieving the image at URL.
  222. - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
  223. - parameter progressBlock: Called when the image downloading progress gets updated.
  224. - parameter completionHandler: Called when the image retrieved and set.
  225. - returns: A task represents the retrieving process.
  226. - note: Both the `progressBlock` and `completionHandler` will be invoked in main thread.
  227. The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
  228. */
  229. public func kf_setImageWithURL(URL: NSURL,
  230. placeholderImage: Image?,
  231. optionsInfo: KingfisherOptionsInfo?,
  232. progressBlock: DownloadProgressBlock?,
  233. completionHandler: CompletionHandler?) -> RetrieveImageTask
  234. {
  235. return kf_setImageWithResource(Resource(downloadURL: URL),
  236. placeholderImage: placeholderImage,
  237. optionsInfo: optionsInfo,
  238. progressBlock: progressBlock,
  239. completionHandler: completionHandler)
  240. }
  241. }
  242. extension ImageView {
  243. /**
  244. Cancel the image download task bounded to the image view if it is running.
  245. Nothing will happen if the downloading has already finished.
  246. */
  247. public func kf_cancelDownloadTask() {
  248. kf_imageTask?.downloadTask?.cancel()
  249. }
  250. }
  251. // MARK: - Associated Object
  252. private var lastURLKey: Void?
  253. private var indicatorKey: Void?
  254. private var showIndicatorWhenLoadingKey: Void?
  255. private var imageTaskKey: Void?
  256. extension ImageView {
  257. /// Get the image URL binded to this image view.
  258. public var kf_webURL: NSURL? {
  259. return objc_getAssociatedObject(self, &lastURLKey) as? NSURL
  260. }
  261. private func kf_setWebURL(URL: NSURL) {
  262. objc_setAssociatedObject(self, &lastURLKey, URL, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  263. }
  264. /// Whether show an animating indicator when the image view is loading an image or not.
  265. /// Default is false.
  266. public var kf_showIndicatorWhenLoading: Bool {
  267. get {
  268. if let result = objc_getAssociatedObject(self, &showIndicatorWhenLoadingKey) as? NSNumber {
  269. return result.boolValue
  270. } else {
  271. return false
  272. }
  273. }
  274. set {
  275. if kf_showIndicatorWhenLoading == newValue {
  276. return
  277. } else {
  278. if newValue {
  279. #if os(OSX)
  280. let indicator = NSProgressIndicator(frame: CGRect(x: 0, y: 0, width: 16, height: 16))
  281. indicator.controlSize = .SmallControlSize
  282. indicator.style = .SpinningStyle
  283. #else
  284. #if os(tvOS)
  285. let indicatorStyle = UIActivityIndicatorViewStyle.White
  286. #else
  287. let indicatorStyle = UIActivityIndicatorViewStyle.Gray
  288. #endif
  289. let indicator = UIActivityIndicatorView(activityIndicatorStyle:indicatorStyle)
  290. indicator.autoresizingMask = [.FlexibleLeftMargin, .FlexibleRightMargin, .FlexibleBottomMargin, .FlexibleTopMargin]
  291. #endif
  292. indicator.kf_center = CGPoint(x: CGRectGetMidX(bounds), y: CGRectGetMidY(bounds))
  293. indicator.hidden = true
  294. self.addSubview(indicator)
  295. kf_setIndicator(indicator)
  296. } else {
  297. kf_indicator?.removeFromSuperview()
  298. kf_setIndicator(nil)
  299. }
  300. objc_setAssociatedObject(self, &showIndicatorWhenLoadingKey, NSNumber(bool: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  301. }
  302. }
  303. }
  304. /// The indicator view showing when loading. This will be `nil` if `kf_showIndicatorWhenLoading` is false.
  305. /// You may want to use this to set the indicator style or color when you set `kf_showIndicatorWhenLoading` to true.
  306. public var kf_indicator: IndicatorView? {
  307. return objc_getAssociatedObject(self, &indicatorKey) as? IndicatorView
  308. }
  309. private func kf_setIndicator(indicator: IndicatorView?) {
  310. objc_setAssociatedObject(self, &indicatorKey, indicator, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  311. }
  312. private var kf_imageTask: RetrieveImageTask? {
  313. return objc_getAssociatedObject(self, &imageTaskKey) as? RetrieveImageTask
  314. }
  315. private func kf_setImageTask(task: RetrieveImageTask?) {
  316. objc_setAssociatedObject(self, &imageTaskKey, task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  317. }
  318. }
  319. extension IndicatorView {
  320. func kf_startAnimating() {
  321. #if os(OSX)
  322. startAnimation(nil)
  323. #else
  324. startAnimating()
  325. #endif
  326. hidden = false
  327. }
  328. func kf_stopAnimating() {
  329. #if os(OSX)
  330. stopAnimation(nil)
  331. #else
  332. stopAnimating()
  333. #endif
  334. hidden = true
  335. }
  336. #if os(OSX)
  337. var kf_center: CGPoint {
  338. get {
  339. return CGPoint(x: frame.origin.x + frame.size.width / 2.0, y: frame.origin.y + frame.size.height / 2.0 )
  340. }
  341. set {
  342. 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)
  343. frame = newFrame
  344. }
  345. }
  346. #else
  347. var kf_center: CGPoint {
  348. get {
  349. return center
  350. }
  351. set {
  352. center = newValue
  353. }
  354. }
  355. #endif
  356. }