ImageView+Kingfisher.swift 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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. */
  119. public func kf_setImageWithResource(resource: Resource,
  120. placeholderImage: Image?,
  121. optionsInfo: KingfisherOptionsInfo?,
  122. completionHandler: CompletionHandler?) -> RetrieveImageTask
  123. {
  124. return kf_setImageWithResource(resource, placeholderImage: placeholderImage, optionsInfo: optionsInfo, progressBlock: nil, completionHandler: completionHandler)
  125. }
  126. /**
  127. Set an image with a URL, a placeholder image, options and completion handler.
  128. - parameter URL: The URL of image.
  129. - parameter placeholderImage: A placeholder image when retrieving the image at URL.
  130. - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
  131. - parameter completionHandler: Called when the image retrieved and set.
  132. - returns: A task represents the retrieving process.
  133. */
  134. public func kf_setImageWithURL(URL: NSURL,
  135. placeholderImage: Image?,
  136. optionsInfo: KingfisherOptionsInfo?,
  137. completionHandler: CompletionHandler?) -> RetrieveImageTask
  138. {
  139. return kf_setImageWithURL(URL, placeholderImage: placeholderImage, optionsInfo: optionsInfo, progressBlock: nil, completionHandler: completionHandler)
  140. }
  141. /**
  142. Set an image with a URL, a placeholder image, options, progress handler and completion handler.
  143. - parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`.
  144. - parameter placeholderImage: A placeholder image when retrieving the image at URL.
  145. - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
  146. - parameter progressBlock: Called when the image downloading progress gets updated.
  147. - parameter completionHandler: Called when the image retrieved and set.
  148. - returns: A task represents the retrieving process.
  149. */
  150. public func kf_setImageWithResource(resource: Resource,
  151. placeholderImage: Image?,
  152. optionsInfo: KingfisherOptionsInfo?,
  153. progressBlock: DownloadProgressBlock?,
  154. completionHandler: CompletionHandler?) -> RetrieveImageTask
  155. {
  156. let showIndicatorWhenLoading = kf_showIndicatorWhenLoading
  157. var indicator: IndicatorView? = nil
  158. if showIndicatorWhenLoading {
  159. indicator = kf_indicator
  160. indicator?.hidden = false
  161. indicator?.kf_startAnimating()
  162. }
  163. image = placeholderImage
  164. kf_setWebURL(resource.downloadURL)
  165. let task = KingfisherManager.sharedManager.retrieveImageWithResource(resource, optionsInfo: optionsInfo,
  166. progressBlock: { receivedSize, totalSize in
  167. if let progressBlock = progressBlock {
  168. dispatch_async(dispatch_get_main_queue(), { () -> Void in
  169. progressBlock(receivedSize: receivedSize, totalSize: totalSize)
  170. })
  171. }
  172. },
  173. completionHandler: {[weak self] image, error, cacheType, imageURL in
  174. dispatch_async_safely_main_queue {
  175. guard let sSelf = self where imageURL == sSelf.kf_webURL else {
  176. completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
  177. return
  178. }
  179. sSelf.kf_setImageTask(nil)
  180. guard let image = image else {
  181. indicator?.kf_stopAnimating()
  182. completionHandler?(image: nil, error: error, cacheType: cacheType, imageURL: imageURL)
  183. return
  184. }
  185. if let transitionItem = optionsInfo?.kf_firstMatchIgnoringAssociatedValue(.Transition(.None)),
  186. case .Transition(let transition) = transitionItem where cacheType == .None {
  187. #if !os(OSX)
  188. UIView.transitionWithView(sSelf, duration: 0.0, options: [],
  189. animations: {
  190. indicator?.kf_stopAnimating()
  191. },
  192. completion: { finished in
  193. UIView.transitionWithView(sSelf, duration: transition.duration,
  194. options: transition.animationOptions,
  195. animations: {
  196. transition.animations?(sSelf, image)
  197. },
  198. completion: { finished in
  199. transition.completion?(finished)
  200. completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
  201. })
  202. })
  203. #endif
  204. } else {
  205. indicator?.kf_stopAnimating()
  206. sSelf.image = image
  207. completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
  208. }
  209. }
  210. })
  211. kf_setImageTask(task)
  212. return task
  213. }
  214. /**
  215. Set an image with a URL, a placeholder image, options, progress handler and completion handler.
  216. - parameter URL: The URL of image.
  217. - parameter placeholderImage: A placeholder image when retrieving the image at URL.
  218. - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
  219. - parameter progressBlock: Called when the image downloading progress gets updated.
  220. - parameter completionHandler: Called when the image retrieved and set.
  221. - returns: A task represents the retrieving process.
  222. */
  223. public func kf_setImageWithURL(URL: NSURL,
  224. placeholderImage: Image?,
  225. optionsInfo: KingfisherOptionsInfo?,
  226. progressBlock: DownloadProgressBlock?,
  227. completionHandler: CompletionHandler?) -> RetrieveImageTask
  228. {
  229. return kf_setImageWithResource(Resource(downloadURL: URL),
  230. placeholderImage: placeholderImage,
  231. optionsInfo: optionsInfo,
  232. progressBlock: progressBlock,
  233. completionHandler: completionHandler)
  234. }
  235. }
  236. extension ImageView {
  237. /**
  238. Cancel the image download task bounded to the image view if it is running.
  239. Nothing will happen if the downloading has already finished.
  240. */
  241. public func kf_cancelDownloadTask() {
  242. kf_imageTask?.downloadTask?.cancel()
  243. }
  244. }
  245. // MARK: - Associated Object
  246. private var lastURLKey: Void?
  247. private var indicatorKey: Void?
  248. private var showIndicatorWhenLoadingKey: Void?
  249. private var imageTaskKey: Void?
  250. extension ImageView {
  251. /// Get the image URL binded to this image view.
  252. public var kf_webURL: NSURL? {
  253. return objc_getAssociatedObject(self, &lastURLKey) as? NSURL
  254. }
  255. private func kf_setWebURL(URL: NSURL) {
  256. objc_setAssociatedObject(self, &lastURLKey, URL, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  257. }
  258. /// Whether show an animating indicator when the image view is loading an image or not.
  259. /// Default is false.
  260. public var kf_showIndicatorWhenLoading: Bool {
  261. get {
  262. if let result = objc_getAssociatedObject(self, &showIndicatorWhenLoadingKey) as? NSNumber {
  263. return result.boolValue
  264. } else {
  265. return false
  266. }
  267. }
  268. set {
  269. if kf_showIndicatorWhenLoading == newValue {
  270. return
  271. } else {
  272. if newValue {
  273. #if os(OSX)
  274. let indicator = NSProgressIndicator(frame: CGRect(x: 0, y: 0, width: 16, height: 16))
  275. indicator.controlSize = .SmallControlSize
  276. indicator.style = .SpinningStyle
  277. #else
  278. #if os(tvOS)
  279. let indicatorStyle = UIActivityIndicatorViewStyle.White
  280. #else
  281. let indicatorStyle = UIActivityIndicatorViewStyle.Gray
  282. #endif
  283. let indicator = UIActivityIndicatorView(activityIndicatorStyle:indicatorStyle)
  284. indicator.autoresizingMask = [.FlexibleLeftMargin, .FlexibleRightMargin, .FlexibleBottomMargin, .FlexibleTopMargin]
  285. #endif
  286. indicator.kf_center = CGPoint(x: CGRectGetMidX(bounds), y: CGRectGetMidY(bounds))
  287. indicator.hidden = true
  288. self.addSubview(indicator)
  289. kf_setIndicator(indicator)
  290. } else {
  291. kf_indicator?.removeFromSuperview()
  292. kf_setIndicator(nil)
  293. }
  294. objc_setAssociatedObject(self, &showIndicatorWhenLoadingKey, NSNumber(bool: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  295. }
  296. }
  297. }
  298. /// The indicator view showing when loading. This will be `nil` if `kf_showIndicatorWhenLoading` is false.
  299. /// You may want to use this to set the indicator style or color when you set `kf_showIndicatorWhenLoading` to true.
  300. public var kf_indicator: IndicatorView? {
  301. return objc_getAssociatedObject(self, &indicatorKey) as? IndicatorView
  302. }
  303. private func kf_setIndicator(indicator: IndicatorView?) {
  304. objc_setAssociatedObject(self, &indicatorKey, indicator, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  305. }
  306. private var kf_imageTask: RetrieveImageTask? {
  307. return objc_getAssociatedObject(self, &imageTaskKey) as? RetrieveImageTask
  308. }
  309. private func kf_setImageTask(task: RetrieveImageTask?) {
  310. objc_setAssociatedObject(self, &imageTaskKey, task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  311. }
  312. }
  313. extension IndicatorView {
  314. func kf_startAnimating() {
  315. #if os(OSX)
  316. startAnimation(nil)
  317. #else
  318. startAnimating()
  319. #endif
  320. hidden = false
  321. }
  322. func kf_stopAnimating() {
  323. #if os(OSX)
  324. stopAnimation(nil)
  325. #else
  326. stopAnimating()
  327. #endif
  328. hidden = true
  329. }
  330. #if os(OSX)
  331. var kf_center: CGPoint {
  332. get {
  333. return CGPoint(x: frame.origin.x + frame.size.width / 2.0, y: frame.origin.y + frame.size.height / 2.0 )
  334. }
  335. set {
  336. 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)
  337. frame = newFrame
  338. }
  339. }
  340. #else
  341. var kf_center: CGPoint {
  342. get {
  343. return center
  344. }
  345. set {
  346. center = newValue
  347. }
  348. }
  349. #endif
  350. }