KF.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. //
  2. // KF.swift
  3. // Kingfisher
  4. //
  5. // Created by onevcat on 2020/09/21.
  6. //
  7. // Copyright (c) 2020 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 canImport(UIKit)
  27. import UIKit
  28. #endif
  29. #if canImport(AppKit) && !targetEnvironment(macCatalyst)
  30. import AppKit
  31. #endif
  32. #if canImport(WatchKit)
  33. import WatchKit
  34. #endif
  35. #if canImport(TVUIKit)
  36. import TVUIKit
  37. #endif
  38. /// A helper type to create image setting tasks in a builder pattern.
  39. /// Use methods in this type to create a `KF.Builder` instance and configure image tasks there.
  40. public enum KF {
  41. /// Creates a builder for a given `Source`.
  42. /// - Parameter source: The `Source` object defines data information from network or a data provider.
  43. /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
  44. /// to start the image loading.
  45. public static func source(_ source: Source) -> KF.Builder {
  46. Builder(source: source)
  47. }
  48. /// Creates a builder for a given `Resource`.
  49. /// - Parameter resource: The `Resource` object defines data information like key or URL.
  50. /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
  51. /// to start the image loading.
  52. public static func resource(_ resource: Resource) -> KF.Builder {
  53. Builder(source: .network(resource))
  54. }
  55. /// Creates a builder for a given `URL` and an optional cache key.
  56. /// - Parameters:
  57. /// - url: The URL where the image should be downloaded.
  58. /// - cacheKey: The key used to store the downloaded image in cache.
  59. /// If `nil`, the `absoluteString` of `url` is used as the cache key.
  60. /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
  61. /// to start the image loading.
  62. public static func url(_ url: URL, cacheKey: String? = nil) -> KF.Builder {
  63. Builder(source: .network(ImageResource(downloadURL: url, cacheKey: cacheKey)))
  64. }
  65. /// Creates a builder for a given `ImageDataProvider`.
  66. /// - Parameter provider: The `ImageDataProvider` object contains information about the data.
  67. /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
  68. /// to start the image loading.
  69. public static func dataProvider(_ provider: ImageDataProvider) -> KF.Builder {
  70. Builder(source: .provider(provider))
  71. }
  72. /// Creates a builder for some given raw data and a cache key.
  73. /// - Parameters:
  74. /// - data: The data object from which the image should be created.
  75. /// - cacheKey: The key used to store the downloaded image in cache.
  76. /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
  77. /// to start the image loading.
  78. public static func data(_ data: Data, cacheKey: String) -> KF.Builder {
  79. Builder(source: .provider(RawImageDataProvider(data: data, cacheKey: cacheKey)))
  80. }
  81. }
  82. extension KF {
  83. /// A builder class to configure an image retrieving task and set it to a holder view or component.
  84. public class Builder {
  85. private let source: Source
  86. #if os(watchOS)
  87. private var placeholder: KFCrossPlatformImage?
  88. #else
  89. private var placeholder: Placeholder?
  90. #endif
  91. public var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions)
  92. public let onFailureDelegate = Delegate<KingfisherError, Void>()
  93. public let onSuccessDelegate = Delegate<RetrieveImageResult, Void>()
  94. public let onProgressDelegate = Delegate<(Int64, Int64), Void>()
  95. init(source: Source) {
  96. self.source = source
  97. }
  98. private var resultHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)? {
  99. {
  100. switch $0 {
  101. case .success(let result):
  102. self.onSuccessDelegate(result)
  103. case .failure(let error):
  104. self.onFailureDelegate(error)
  105. }
  106. }
  107. }
  108. private var progressBlock: DownloadProgressBlock {
  109. { self.onProgressDelegate(($0, $1)) }
  110. }
  111. }
  112. }
  113. extension KF.Builder {
  114. #if !os(watchOS)
  115. /// Builds the image task request and sets it to an image view.
  116. /// - Parameter imageView: The image view which loads the task and should be set with the image.
  117. /// - Returns: A task represents the image downloading, if initialized.
  118. /// This value is `nil` if the image is being loaded from cache.
  119. @discardableResult
  120. public func set(to imageView: KFCrossPlatformImageView) -> DownloadTask? {
  121. imageView.kf.setImage(
  122. with: source,
  123. placeholder: placeholder,
  124. parsedOptions: options,
  125. progressBlock: progressBlock,
  126. completionHandler: resultHandler
  127. )
  128. }
  129. /// Builds the image task request and sets it to an `NSTextAttachment` object.
  130. /// - Parameters:
  131. /// - attachment: The text attachment object which loads the task and should be set with the image.
  132. /// - attributedView: The owner of the attributed string which this `NSTextAttachment` is added.
  133. /// - Returns: A task represents the image downloading, if initialized.
  134. /// This value is `nil` if the image is being loaded from cache.
  135. @discardableResult
  136. public func set(to attachment: NSTextAttachment, attributedView: KFCrossPlatformView) -> DownloadTask? {
  137. let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
  138. return attachment.kf.setImage(
  139. with: source,
  140. attributedView: attributedView,
  141. placeholder: placeholderImage,
  142. parsedOptions: options,
  143. progressBlock: progressBlock,
  144. completionHandler: resultHandler
  145. )
  146. }
  147. #if canImport(UIKit)
  148. /// Builds the image task request and sets it to a button.
  149. /// - Parameters:
  150. /// - button: The button which loads the task and should be set with the image.
  151. /// - state: The button state to which the image should be set.
  152. /// - Returns: A task represents the image downloading, if initialized.
  153. /// This value is `nil` if the image is being loaded from cache.
  154. @discardableResult
  155. public func set(to button: UIButton, for state: UIControl.State) -> DownloadTask? {
  156. let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
  157. return button.kf.setImage(
  158. with: source,
  159. for: state,
  160. placeholder: placeholderImage,
  161. parsedOptions: options,
  162. progressBlock: progressBlock,
  163. completionHandler: resultHandler
  164. )
  165. }
  166. /// Builds the image task request and sets it to the background image for a button.
  167. /// - Parameters:
  168. /// - button: The button which loads the task and should be set with the image.
  169. /// - state: The button state to which the image should be set.
  170. /// - Returns: A task represents the image downloading, if initialized.
  171. /// This value is `nil` if the image is being loaded from cache.
  172. @discardableResult
  173. public func setBackground(to button: UIButton, for state: UIControl.State) -> DownloadTask? {
  174. let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
  175. return button.kf.setBackgroundImage(
  176. with: source,
  177. for: state,
  178. placeholder: placeholderImage,
  179. parsedOptions: options,
  180. progressBlock: progressBlock,
  181. completionHandler: resultHandler
  182. )
  183. }
  184. #endif // end of canImport(UIKit)
  185. #if canImport(AppKit) && !targetEnvironment(macCatalyst)
  186. /// Builds the image task request and sets it to a button.
  187. /// - Parameter button: The button which loads the task and should be set with the image.
  188. /// - Returns: A task represents the image downloading, if initialized.
  189. /// This value is `nil` if the image is being loaded from cache.
  190. @discardableResult
  191. public func set(to button: NSButton) -> DownloadTask? {
  192. let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
  193. return button.kf.setImage(
  194. with: source,
  195. placeholder: placeholderImage,
  196. parsedOptions: options,
  197. progressBlock: progressBlock,
  198. completionHandler: resultHandler
  199. )
  200. }
  201. /// Builds the image task request and sets it to the alternative image for a button.
  202. /// - Parameter button: The button which loads the task and should be set with the image.
  203. /// - Returns: A task represents the image downloading, if initialized.
  204. /// This value is `nil` if the image is being loaded from cache.
  205. @discardableResult
  206. public func setAlternative(to button: NSButton) -> DownloadTask? {
  207. let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
  208. return button.kf.setAlternateImage(
  209. with: source,
  210. placeholder: placeholderImage,
  211. parsedOptions: options,
  212. progressBlock: progressBlock,
  213. completionHandler: resultHandler
  214. )
  215. }
  216. #endif // end of canImport(AppKit)
  217. #endif // end of !os(watchOS)
  218. #if canImport(WatchKit)
  219. /// Builds the image task request and sets it to a `WKInterfaceImage` object.
  220. /// - Parameter interfaceImage: The watch interface image which loads the task and should be set with the image.
  221. /// - Returns: A task represents the image downloading, if initialized.
  222. /// This value is `nil` if the image is being loaded from cache.
  223. @discardableResult
  224. public func set(to interfaceImage: WKInterfaceImage) -> DownloadTask? {
  225. let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
  226. return interfaceImage.kf.setImage(
  227. with: source,
  228. placeholder: placeholder,
  229. parsedOptions: options,
  230. progressBlock: progressBlock,
  231. completionHandler: resultHandler
  232. )
  233. }
  234. #endif // end of canImport(WatchKit)
  235. #if canImport(TVUIKit)
  236. /// Builds the image task request and sets it to a TV monogram view.
  237. /// - Parameter monogramView: The monogram view which loads the task and should be set with the image.
  238. /// - Returns: A task represents the image downloading, if initialized.
  239. /// This value is `nil` if the image is being loaded from cache.
  240. @available(tvOS 12.0, *)
  241. @discardableResult
  242. public func set(to monogramView: TVMonogramView) -> DownloadTask? {
  243. let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
  244. return monogramView.kf.setImage(
  245. with: source,
  246. placeholder: placeholderImage,
  247. parsedOptions: options,
  248. progressBlock: progressBlock,
  249. completionHandler: resultHandler
  250. )
  251. }
  252. #endif // end of canImport(TVUIKit)
  253. }
  254. #if !os(watchOS)
  255. extension KF.Builder {
  256. #if os(iOS) || os(tvOS)
  257. /// Sets a placeholder which is used while retrieving the image.
  258. /// - Parameter placeholder: A placeholder to show while retrieving the image from its source.
  259. /// - Returns: A `KF.Builder` with changes applied.
  260. public func placeholder(_ placeholder: Placeholder?) -> Self {
  261. self.placeholder = placeholder
  262. return self
  263. }
  264. #endif
  265. /// Sets a placeholder image which is used while retrieving the image.
  266. /// - Parameter placeholder: An image to show while retrieving the image from its source.
  267. /// - Returns: A `KF.Builder` with changes applied.
  268. public func placeholder(_ image: KFCrossPlatformImage?) -> Self {
  269. self.placeholder = image
  270. return self
  271. }
  272. }
  273. #endif
  274. extension KF.Builder {
  275. #if os(iOS) || os(tvOS)
  276. /// Sets the transition for the image task.
  277. /// - Parameter transition: The desired transition effect when setting the image to image view.
  278. /// - Returns: A `KF.Builder` with changes applied.
  279. ///
  280. /// Kingfisher will use the `transition` to animate the image in if it is downloaded from web.
  281. /// The transition will not happen when the
  282. /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when
  283. /// the image being retrieved from cache, also call `forceRefresh()` on the returned `KF.Builder`.
  284. public func transition(_ transition: ImageTransition) -> Self {
  285. options.transition = transition
  286. return self
  287. }
  288. /// Sets a fade transition for the image task.
  289. /// - Parameter duration: The duration of the fade transition.
  290. /// - Returns: A `KF.Builder` with changes applied.
  291. ///
  292. /// Kingfisher will use the fade transition to animate the image in if it is downloaded from web.
  293. /// The transition will not happen when the
  294. /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when
  295. /// the image being retrieved from cache, also call `forceRefresh()` on the returned `KF.Builder`.
  296. public func fade(duration: TimeInterval) -> Self {
  297. options.transition = .fade(duration)
  298. return self
  299. }
  300. #endif
  301. /// Sets whether the image setting for an image view should happen with transition even when retrieved from cache.
  302. /// - Parameter enabled: Enable the force transition or not.
  303. /// - Returns: A `KF.Builder` with changes applied.
  304. public func forceTransition(_ enabled: Bool = true) -> Self {
  305. options.forceTransition = enabled
  306. return self
  307. }
  308. /// Sets whether keeping the existing image of image view while setting another image to it.
  309. /// - Parameter enabled: Whether the existing image should be kept.
  310. /// - Returns: A `KF.Builder` with changes applied.
  311. ///
  312. /// By setting this option, the placeholder image parameter of image view extension method
  313. /// will be ignored and the current image will be kept while loading or downloading the new image.
  314. ///
  315. public func keepCurrentImageWhileLoading(_ enabled: Bool = true) -> Self {
  316. options.keepCurrentImageWhileLoading = enabled
  317. return self
  318. }
  319. /// Sets whether only the first frame from an animated image file should be loaded as a single image.
  320. /// - Parameter enabled: Whether the only the first frame should be loaded.
  321. /// - Returns: A `KF.Builder` with changes applied.
  322. ///
  323. /// Loading an animated images may take too much memory. It will be useful when you want to display a
  324. /// static preview of the first frame from an animated image.
  325. ///
  326. /// This option will be ignored if the target image is not animated image data.
  327. ///
  328. public func onlyLoadFirstFrame(_ enabled: Bool = true) -> Self {
  329. options.onlyLoadFirstFrame = enabled
  330. return self
  331. }
  332. /// Sets the image that will be used if an image retrieving task fails.
  333. /// - Parameter image: The image that will be used when something goes wrong.
  334. /// - Returns: A `KF.Builder` with changes applied.
  335. ///
  336. /// If set and an image retrieving error occurred Kingfisher will set provided image (or empty)
  337. /// in place of requested one. It's useful when you don't want to show placeholder
  338. /// during loading time but wants to use some default image when requests will be failed.
  339. ///
  340. public func onFailureImage(_ image: KFCrossPlatformImage?) -> Self {
  341. options.onFailureImage = .some(image)
  342. return self
  343. }
  344. /// Enables progressive image loading with a specified `ImageProgressive` setting to process the
  345. /// progressive JPEG data and display it in a progressive way.
  346. /// - Parameter progressive: The progressive settings which is used while loading.
  347. /// - Returns: A `KF.Builder` with changes applied.
  348. public func progressiveJPEG(_ progressive: ImageProgressive? = .default) -> Self {
  349. options.progressiveJPEG = progressive
  350. return self
  351. }
  352. }
  353. // MARK: - Redirect Handler
  354. extension KF {
  355. /// Represents the detail information when a task redirect happens. It is wrapping necessary information for a
  356. /// `ImageDownloadRedirectHandler`. See that protocol for more information.
  357. public struct RedirectPayload {
  358. /// The related session data task when the redirect happens. It is
  359. /// the current `SessionDataTask` which triggers this redirect.
  360. public let task: SessionDataTask
  361. /// The response received during redirection.
  362. public let response: HTTPURLResponse
  363. /// The request for redirection which can be modified.
  364. public let newRequest: URLRequest
  365. /// A closure for being called with modified request.
  366. public let completionHandler: (URLRequest?) -> Void
  367. }
  368. }