HasImageComponent+Kingfisher.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. //
  2. // KingfisherHasImageComponent+Kingfisher.swift
  3. // Kingfisher
  4. //
  5. // Created by JH on 2023/12/5.
  6. //
  7. // Copyright (c) 2023 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. public protocol KingfisherHasImageComponent: KingfisherCompatible {
  27. @MainActor var image: KFCrossPlatformImage? { set get }
  28. }
  29. #if canImport(AppKit) && !targetEnvironment(macCatalyst)
  30. import AppKit
  31. @available(macOS 13.0, *)
  32. extension NSComboButton: KingfisherHasImageComponent {}
  33. @available(macOS 13.0, *)
  34. extension NSColorWell: KingfisherHasImageComponent {}
  35. extension NSTableViewRowAction: KingfisherHasImageComponent {}
  36. extension NSMenuItem: KingfisherHasImageComponent {}
  37. extension NSPathControlItem: KingfisherHasImageComponent {}
  38. extension NSToolbarItem: KingfisherHasImageComponent {}
  39. extension NSTabViewItem: KingfisherHasImageComponent {}
  40. extension NSStatusItem: KingfisherHasImageComponent {}
  41. extension NSCell: KingfisherHasImageComponent {}
  42. #endif
  43. #if canImport(UIKit) && !os(watchOS)
  44. import UIKit
  45. @available(iOS 13.0, tvOS 13.0, *)
  46. extension UIAction: KingfisherHasImageComponent {}
  47. @available(iOS 13.0, tvOS 13.0, *)
  48. extension UICommand: KingfisherHasImageComponent {}
  49. extension UIBarItem: KingfisherHasImageComponent {}
  50. #endif
  51. #if !os(watchOS)
  52. @MainActor
  53. extension KingfisherWrapper where Base: KingfisherHasImageComponent {
  54. // MARK: Setting Image
  55. /// Sets an image to the image view with a ``Source``.
  56. ///
  57. /// - Parameters:
  58. /// - source: The ``Source`` object that defines data information from the network or a data provider.
  59. /// - placeholder: A placeholder to show while retrieving the image from the given `source`.
  60. /// - options: A set of options to define image setting behaviors. See ``KingfisherOptionsInfo`` for more.
  61. /// - progressBlock: Called when the image downloading progress is updated. If the response does not contain an
  62. /// `expectedContentLength`, this block will not be called.
  63. /// - completionHandler: Called when the image retrieval and setting are finished.
  64. /// - Returns: A task that represents the image downloading.
  65. ///
  66. /// This is the easiest way to use Kingfisher to boost the image setting process from a source. Since all parameters
  67. /// have a default value except the `source`, you can set an image from a certain URL to an image view like this:
  68. ///
  69. /// ```swift
  70. /// // Set image from a network source.
  71. /// let url = URL(string: "https://example.com/image.png")!
  72. /// imageView.kf.setImage(with: .network(url))
  73. ///
  74. /// // Or set image from a data provider.
  75. /// let provider = LocalFileImageDataProvider(fileURL: fileURL)
  76. /// imageView.kf.setImage(with: .provider(provider))
  77. /// ```
  78. ///
  79. /// For both ``Source/network(_:)`` and ``Source/provider(_:)`` sources, there are corresponding view extension
  80. /// methods. So the code above is equivalent to:
  81. ///
  82. /// ```swift
  83. /// imageView.kf.setImage(with: url)
  84. /// imageView.kf.setImage(with: provider)
  85. /// ```
  86. ///
  87. /// Internally, this method will use ``KingfisherManager`` to get the source. Since this method will perform UI
  88. /// changes, it is your responsibility to call it from the main thread.
  89. ///
  90. /// > Both `progressBlock` and `completionHandler` will also be executed in the main thread.
  91. @discardableResult
  92. public func setImage(
  93. with source: Source?,
  94. placeholder: KFCrossPlatformImage? = nil,
  95. options: KingfisherOptionsInfo? = nil,
  96. progressBlock: DownloadProgressBlock? = nil,
  97. completionHandler: (@MainActor @Sendable (Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil
  98. ) -> DownloadTask?
  99. {
  100. let options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty))
  101. return setImage(with: source, placeholder: placeholder, parsedOptions: options, progressBlock: progressBlock, completionHandler: completionHandler)
  102. }
  103. /// Sets an image to the image view with a ``Source``.
  104. ///
  105. /// - Parameters:
  106. /// - source: The ``Source`` object that defines data information from the network or a data provider.
  107. /// - placeholder: A placeholder to show while retrieving the image from the given `source`.
  108. /// - options: A set of options to define image setting behaviors. See ``KingfisherOptionsInfo`` for more.
  109. /// - completionHandler: Called when the image retrieval and setting are finished.
  110. /// - Returns: A task that represents the image downloading.
  111. ///
  112. /// This is the easiest way to use Kingfisher to boost the image setting process from a source. Since all parameters
  113. /// have a default value except the `source`, you can set an image from a certain URL to an image view like this:
  114. ///
  115. /// ```swift
  116. /// // Set image from a network source.
  117. /// let url = URL(string: "https://example.com/image.png")!
  118. /// imageView.kf.setImage(with: .network(url))
  119. ///
  120. /// // Or set image from a data provider.
  121. /// let provider = LocalFileImageDataProvider(fileURL: fileURL)
  122. /// imageView.kf.setImage(with: .provider(provider))
  123. /// ```
  124. ///
  125. /// For both ``Source/network(_:)`` and ``Source/provider(_:)`` sources, there are corresponding view extension
  126. /// methods. So the code above is equivalent to:
  127. ///
  128. /// ```swift
  129. /// imageView.kf.setImage(with: url)
  130. /// imageView.kf.setImage(with: provider)
  131. /// ```
  132. ///
  133. /// Internally, this method will use ``KingfisherManager`` to get the source. Since this method will perform UI
  134. /// changes, it is your responsibility to call it from the main thread.
  135. ///
  136. /// > Both `progressBlock` and `completionHandler` will also be executed in the main thread.
  137. @discardableResult
  138. public func setImage(
  139. with source: Source?,
  140. placeholder: KFCrossPlatformImage? = nil,
  141. options: KingfisherOptionsInfo? = nil,
  142. completionHandler: (@MainActor @Sendable (Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil
  143. ) -> DownloadTask?
  144. {
  145. return setImage(
  146. with: source,
  147. placeholder: placeholder,
  148. options: options,
  149. progressBlock: nil,
  150. completionHandler: completionHandler
  151. )
  152. }
  153. /// Sets an image to the image view with a requested ``Resource``.
  154. ///
  155. /// - Parameters:
  156. /// - resource: The ``Resource`` object contains information about the resource.
  157. /// - placeholder: A placeholder to show while retrieving the image from the given `source`.
  158. /// - options: A set of options to define image setting behaviors. See ``KingfisherOptionsInfo`` for more.
  159. /// - progressBlock: Called when the image downloading progress is updated. If the response does not contain an
  160. /// `expectedContentLength`, this block will not be called.
  161. /// - completionHandler: Called when the image retrieval and setting are finished.
  162. /// - Returns: A task that represents the image downloading.
  163. ///
  164. /// This is the easiest way to use Kingfisher to boost the image setting process from a source. Since all parameters
  165. /// have a default value except the `source`, you can set an image from a certain URL to an image view like this:
  166. ///
  167. /// ```swift
  168. /// // Set image from a URL resource.
  169. /// let url = URL(string: "https://example.com/image.png")!
  170. /// imageView.kf.setImage(with: url)
  171. /// ```
  172. ///
  173. /// Internally, this method will use ``KingfisherManager`` to get the source. Since this method will perform UI
  174. /// changes, it is your responsibility to call it from the main thread.
  175. ///
  176. /// > Both `progressBlock` and `completionHandler` will also be executed in the main thread.
  177. @discardableResult
  178. public func setImage(
  179. with resource: Resource?,
  180. placeholder: KFCrossPlatformImage? = nil,
  181. options: KingfisherOptionsInfo? = nil,
  182. progressBlock: DownloadProgressBlock? = nil,
  183. completionHandler: (@MainActor @Sendable (Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil
  184. ) -> DownloadTask?
  185. {
  186. return setImage(
  187. with: resource?.convertToSource(),
  188. placeholder: placeholder,
  189. options: options,
  190. progressBlock: progressBlock,
  191. completionHandler: completionHandler)
  192. }
  193. /// Sets an image to the image view with a requested ``Resource``.
  194. ///
  195. /// - Parameters:
  196. /// - resource: The ``Resource`` object contains information about the resource.
  197. /// - placeholder: A placeholder to show while retrieving the image from the given `source`.
  198. /// - options: A set of options to define image setting behaviors. See ``KingfisherOptionsInfo`` for more.
  199. /// - completionHandler: Called when the image retrieval and setting are finished.
  200. /// - Returns: A task that represents the image downloading.
  201. ///
  202. /// This is the easiest way to use Kingfisher to boost the image setting process from a source. Since all parameters
  203. /// have a default value except the `source`, you can set an image from a certain URL to an image view like this:
  204. ///
  205. /// ```swift
  206. /// // Set image from a URL resource.
  207. /// let url = URL(string: "https://example.com/image.png")!
  208. /// imageView.kf.setImage(with: url)
  209. /// ```
  210. ///
  211. /// Internally, this method will use ``KingfisherManager`` to get the source. Since this method will perform UI
  212. /// changes, it is your responsibility to call it from the main thread.
  213. ///
  214. /// > Both `progressBlock` and `completionHandler` will also be executed in the main thread.
  215. @discardableResult
  216. public func setImage(
  217. with resource: Resource?,
  218. placeholder: KFCrossPlatformImage? = nil,
  219. options: KingfisherOptionsInfo? = nil,
  220. completionHandler: (@MainActor @Sendable (Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil
  221. ) -> DownloadTask?
  222. {
  223. return setImage(
  224. with: resource,
  225. placeholder: placeholder,
  226. options: options,
  227. progressBlock: nil,
  228. completionHandler: completionHandler
  229. )
  230. }
  231. /// Sets an image to the image view with a ``ImageDataProvider``.
  232. ///
  233. /// - Parameters:
  234. /// - provider: The ``ImageDataProvider`` object that defines data information from the data provider.
  235. /// - placeholder: A placeholder to show while retrieving the image from the given `source`.
  236. /// - options: A set of options to define image setting behaviors. See ``KingfisherOptionsInfo`` for more.
  237. /// - progressBlock: Called when the image downloading progress is updated. If the response does not contain an
  238. /// `expectedContentLength`, this block will not be called.
  239. /// - completionHandler: Called when the image retrieval and setting are finished.
  240. /// - Returns: A task that represents the image downloading.
  241. ///
  242. /// Internally, this method will use ``KingfisherManager`` to get the source. Since this method will perform UI
  243. /// changes, it is your responsibility to call it from the main thread.
  244. ///
  245. /// > Both `progressBlock` and `completionHandler` will also be executed in the main thread.
  246. @discardableResult
  247. public func setImage(
  248. with provider: ImageDataProvider?,
  249. placeholder: KFCrossPlatformImage? = nil,
  250. options: KingfisherOptionsInfo? = nil,
  251. progressBlock: DownloadProgressBlock? = nil,
  252. completionHandler: (@MainActor @Sendable (Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil
  253. ) -> DownloadTask?
  254. {
  255. return setImage(
  256. with: provider.map { .provider($0) },
  257. placeholder: placeholder,
  258. options: options,
  259. progressBlock: progressBlock,
  260. completionHandler: completionHandler)
  261. }
  262. /// Sets an image to the image view with a ``ImageDataProvider``.
  263. ///
  264. /// - Parameters:
  265. /// - provider: The ``ImageDataProvider`` object that defines data information from the data provider.
  266. /// - placeholder: A placeholder to show while retrieving the image from the given `source`.
  267. /// - options: A set of options to define image setting behaviors. See ``KingfisherOptionsInfo`` for more.
  268. /// - completionHandler: Called when the image retrieval and setting are finished.
  269. /// - Returns: A task that represents the image downloading.
  270. ///
  271. /// Internally, this method will use ``KingfisherManager`` to get the source. Since this method will perform UI
  272. /// changes, it is your responsibility to call it from the main thread.
  273. ///
  274. /// > Both `progressBlock` and `completionHandler` will also be executed in the main thread.
  275. @discardableResult
  276. public func setImage(
  277. with provider: ImageDataProvider?,
  278. placeholder: KFCrossPlatformImage? = nil,
  279. options: KingfisherOptionsInfo? = nil,
  280. completionHandler: (@MainActor @Sendable (Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil
  281. ) -> DownloadTask?
  282. {
  283. return setImage(
  284. with: provider,
  285. placeholder: placeholder,
  286. options: options,
  287. progressBlock: nil,
  288. completionHandler: completionHandler
  289. )
  290. }
  291. func setImage(
  292. with source: Source?,
  293. placeholder: KFCrossPlatformImage? = nil,
  294. parsedOptions: KingfisherParsedOptionsInfo,
  295. progressBlock: DownloadProgressBlock? = nil,
  296. completionHandler: (@MainActor @Sendable (Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil
  297. ) -> DownloadTask?
  298. {
  299. var mutatingSelf = self
  300. guard let source = source else {
  301. mutatingSelf.taskIdentifier = nil
  302. completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource)))
  303. return nil
  304. }
  305. var options = parsedOptions
  306. let isEmptyImage = base.image == nil && self.placeholder == nil
  307. if !options.keepCurrentImageWhileLoading || isEmptyImage {
  308. // Always set placeholder while there is no image/placeholder yet.
  309. mutatingSelf.placeholder = placeholder
  310. }
  311. let issuedIdentifier = Source.Identifier.next()
  312. mutatingSelf.taskIdentifier = issuedIdentifier
  313. if let block = progressBlock {
  314. options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)]
  315. }
  316. let task = KingfisherManager.shared.retrieveImage(
  317. with: source,
  318. options: options,
  319. downloadTaskUpdated: { task in
  320. Task { @MainActor in mutatingSelf.imageTask = task }
  321. },
  322. progressiveImageSetter: { self.base.image = $0 },
  323. referenceTaskIdentifierChecker: { issuedIdentifier == self.taskIdentifier },
  324. completionHandler: { result in
  325. CallbackQueueMain.currentOrAsync {
  326. guard issuedIdentifier == self.taskIdentifier else {
  327. let reason: KingfisherError.ImageSettingErrorReason
  328. do {
  329. let value = try result.get()
  330. reason = .notCurrentSourceTask(result: value, error: nil, source: source)
  331. } catch {
  332. reason = .notCurrentSourceTask(result: nil, error: error, source: source)
  333. }
  334. let error = KingfisherError.imageSettingError(reason: reason)
  335. completionHandler?(.failure(error))
  336. return
  337. }
  338. mutatingSelf.imageTask = nil
  339. mutatingSelf.taskIdentifier = nil
  340. switch result {
  341. case .success(let value):
  342. mutatingSelf.placeholder = nil
  343. self.base.image = value.image
  344. completionHandler?(result)
  345. case .failure:
  346. if let image = options.onFailureImage {
  347. mutatingSelf.placeholder = nil
  348. self.base.image = image
  349. }
  350. completionHandler?(result)
  351. }
  352. }
  353. }
  354. )
  355. mutatingSelf.imageTask = task
  356. return task
  357. }
  358. // MARK: Cancelling Downloading Task
  359. /// Cancels the image download task of the image view if it is running.
  360. ///
  361. /// Nothing will happen if the downloading has already finished.
  362. public func cancelDownloadTask() {
  363. imageTask?.cancel()
  364. }
  365. }
  366. // MARK: - Associated Object
  367. @MainActor private var taskIdentifierKey: Void?
  368. @MainActor private var indicatorKey: Void?
  369. @MainActor private var indicatorTypeKey: Void?
  370. @MainActor private var placeholderKey: Void?
  371. @MainActor private var imageTaskKey: Void?
  372. @MainActor
  373. extension KingfisherWrapper where Base: KingfisherHasImageComponent {
  374. // MARK: Properties
  375. public private(set) var taskIdentifier: Source.Identifier.Value? {
  376. get {
  377. let box: Box<Source.Identifier.Value>? = getAssociatedObject(base, &taskIdentifierKey)
  378. return box?.value
  379. }
  380. set {
  381. let box = newValue.map { Box($0) }
  382. setRetainedAssociatedObject(base, &taskIdentifierKey, box)
  383. }
  384. }
  385. private var imageTask: DownloadTask? {
  386. get { return getAssociatedObject(base, &imageTaskKey) }
  387. set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)}
  388. }
  389. /// Represents the ``Placeholder`` used for this image view.
  390. ///
  391. /// A ``Placeholder`` will be shown in the view while it is downloading an image.
  392. public private(set) var placeholder: KFCrossPlatformImage? {
  393. get { return getAssociatedObject(base, &placeholderKey) }
  394. set {
  395. if let previousPlaceholder = placeholder {
  396. previousPlaceholder.remove(from: base)
  397. }
  398. if let newPlaceholder = newValue {
  399. newPlaceholder.add(to: base)
  400. } else {
  401. base.image = nil
  402. }
  403. setRetainedAssociatedObject(base, &placeholderKey, newValue)
  404. }
  405. }
  406. }
  407. #endif