KF.swift 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982
  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. /// A helper type to create image setting tasks in a builder pattern.
  36. /// Use methods in this type to create a `KF.Builder` instance and configure image tasks there.
  37. public enum KF {
  38. /// Creates a builder for a given `Source`.
  39. /// - Parameter source: The `Source` object defines data information from network or a data provider.
  40. /// - Returns: A `KF.Builder` for future configuration or image setting.
  41. public static func source(_ source: Source) -> KF.Builder {
  42. Builder(source: source)
  43. }
  44. /// Creates a builder for a given `Resource`.
  45. /// - Parameter resource: The `Resource` object defines data information like key or URL.
  46. /// - Returns: A `KF.Builder` for future configuration or image setting.
  47. public static func resource(_ resource: Resource) -> KF.Builder {
  48. Builder(source: .network(resource))
  49. }
  50. /// Creates a builder for a given `URL` and an optional cache key.
  51. /// - Parameters:
  52. /// - url: The URL where the image should be downloaded.
  53. /// - cacheKey: The key used to store the downloaded image in cache.
  54. /// If `nil`, the `absoluteString` of `url` is used as the cache key.
  55. /// - Returns: A `KF.Builder` for future configuration or image setting.
  56. public static func url(_ url: URL, cacheKey: String? = nil) -> KF.Builder {
  57. Builder(source: .network(ImageResource(downloadURL: url, cacheKey: cacheKey)))
  58. }
  59. /// Creates a builder for a given `ImageDataProvider`.
  60. /// - Parameter provider: The `ImageDataProvider` object contains information about the data.
  61. /// - Returns: A `KF.Builder` for future configuration or image setting.
  62. public static func dataProvider(_ provider: ImageDataProvider) -> KF.Builder {
  63. Builder(source: .provider(provider))
  64. }
  65. /// Creates a builder for some given raw data and a cache key.
  66. /// - Parameters:
  67. /// - data: The data object from which the image should be created.
  68. /// - cacheKey: The key used to store the downloaded image in cache.
  69. /// - Returns: A `KF.Builder` for future configuration or image setting.
  70. public static func data(_ data: Data, cacheKey: String) -> KF.Builder {
  71. Builder(source: .provider(RawImageDataProvider(data: data, cacheKey: cacheKey)))
  72. }
  73. }
  74. extension KF {
  75. /// A builder class to configure an image retrieving task and set it to a holder view or component.
  76. public class Builder {
  77. private let source: Source
  78. #if os(watchOS)
  79. private var placeholder: KFCrossPlatformImage?
  80. #else
  81. private var placeholder: Placeholder?
  82. #endif
  83. private var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions)
  84. private var progressBlock: DownloadProgressBlock?
  85. private var doneBlock: ((RetrieveImageResult) -> Void)?
  86. private var errorBlock: ((KingfisherError) -> Void)?
  87. init(source: Source) {
  88. self.source = source
  89. }
  90. private var resultHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)? {
  91. if doneBlock == nil && errorBlock == nil {
  92. return nil
  93. }
  94. return { result in
  95. switch result {
  96. case .success(let result):
  97. self.doneBlock?(result)
  98. case .failure(let error):
  99. self.errorBlock?(error)
  100. }
  101. }
  102. }
  103. }
  104. }
  105. extension KF.Builder {
  106. #if !os(watchOS)
  107. /// Builds the image task request and sets it to an image view.
  108. /// - Parameter imageView: The image view which loads the task and should be set with the image.
  109. /// - Returns: A task represents the image downloading, if initialized.
  110. @discardableResult
  111. public func set(to imageView: KFCrossPlatformImageView) -> DownloadTask? {
  112. imageView.kf.setImage(
  113. with: source,
  114. placeholder: placeholder,
  115. parsedOptions: options,
  116. progressBlock: progressBlock,
  117. completionHandler: resultHandler
  118. )
  119. }
  120. /// Builds the image task request and sets it to an `NSTextAttachment` object.
  121. /// - Parameters:
  122. /// - attachment: The text attachment object which loads the task and should be set with the image.
  123. /// - attributedView: The owner of the attributed string which this `NSTextAttachment` is added.
  124. /// - Returns: A task represents the image downloading, if initialized.
  125. @discardableResult
  126. public func set(to attachment: NSTextAttachment, attributedView: KFCrossPlatformView) -> DownloadTask? {
  127. let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
  128. return attachment.kf.setImage(
  129. with: source,
  130. attributedView: attributedView,
  131. placeholder: placeholderImage,
  132. parsedOptions: options,
  133. progressBlock: progressBlock,
  134. completionHandler: resultHandler
  135. )
  136. }
  137. #if canImport(UIKit)
  138. /// Builds the image task request and sets it to a button.
  139. /// - Parameters:
  140. /// - button: The button which loads the task and should be set with the image.
  141. /// - state: The button state to which the image should be set.
  142. /// - Returns: A task represents the image downloading, if initialized.
  143. @discardableResult
  144. public func set(to button: UIButton, for state: UIControl.State) -> DownloadTask? {
  145. let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
  146. return button.kf.setImage(
  147. with: source,
  148. for: state,
  149. placeholder: placeholderImage,
  150. parsedOptions: options,
  151. progressBlock: progressBlock,
  152. completionHandler: resultHandler
  153. )
  154. }
  155. /// Builds the image task request and sets it to the background image for a button.
  156. /// - Parameters:
  157. /// - button: The button which loads the task and should be set with the image.
  158. /// - state: The button state to which the image should be set.
  159. /// - Returns: A task represents the image downloading, if initialized.
  160. @discardableResult
  161. public func setBackground(to button: UIButton, for state: UIControl.State) -> DownloadTask? {
  162. let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
  163. return button.kf.setBackgroundImage(
  164. with: source,
  165. for: state,
  166. placeholder: placeholderImage,
  167. parsedOptions: options,
  168. progressBlock: progressBlock,
  169. completionHandler: resultHandler
  170. )
  171. }
  172. #endif // end of canImport(UIKit)
  173. #if canImport(AppKit) && !targetEnvironment(macCatalyst)
  174. /// Builds the image task request and sets it to a button.
  175. /// - Parameter button: The button which loads the task and should be set with the image.
  176. /// - Returns: A task represents the image downloading, if initialized.
  177. @discardableResult
  178. public func set(to button: NSButton) -> DownloadTask? {
  179. let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
  180. return button.kf.setImage(
  181. with: source,
  182. placeholder: placeholderImage,
  183. parsedOptions: options,
  184. progressBlock: progressBlock,
  185. completionHandler: resultHandler
  186. )
  187. }
  188. /// Builds the image task request and sets it to the alternative image for a button.
  189. /// - Parameter button: The button which loads the task and should be set with the image.
  190. /// - Returns: A task represents the image downloading, if initialized.
  191. @discardableResult
  192. public func setAlternative(to button: NSButton) -> DownloadTask? {
  193. let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
  194. return button.kf.setAlternateImage(
  195. with: source,
  196. placeholder: placeholderImage,
  197. parsedOptions: options,
  198. progressBlock: progressBlock,
  199. completionHandler: resultHandler
  200. )
  201. }
  202. #endif // end of canImport(AppKit)
  203. #endif // end of !os(watchOS)
  204. #if canImport(WatchKit)
  205. /// Builds the image task request and sets it to a `WKInterfaceImage` object.
  206. /// - Parameter interfaceImage: The watch interface image which loads the task and should be set with the image.
  207. /// - Returns: A task represents the image downloading, if initialized.
  208. @discardableResult
  209. public func set(to interfaceImage: WKInterfaceImage) -> DownloadTask? {
  210. return interfaceImage.kf.setImage(
  211. with: source,
  212. placeholder: placeholder,
  213. parsedOptions: options,
  214. progressBlock: progressBlock,
  215. completionHandler: resultHandler
  216. )
  217. }
  218. #endif // end of canImport(WatchKit)
  219. }
  220. extension KF.Builder {
  221. /// Sets the progress block to current builder.
  222. /// - Parameter block: Called when the image downloading progress gets updated. If the response does not contain an
  223. /// `expectedContentLength`, this block will not be called.
  224. /// - Returns: A `KF.Builder` with changes applied.
  225. public func progress(_ block: DownloadProgressBlock?) -> Self {
  226. self.progressBlock = block
  227. return self
  228. }
  229. /// Sets the the done block to current builder.
  230. /// - Parameter block: Called when the image task successfully completes and the the image set is done.
  231. /// - Returns: A `KF.Builder` with changes applied.
  232. public func done(_ block: ((RetrieveImageResult) -> Void)?) -> Self {
  233. self.doneBlock = block
  234. return self
  235. }
  236. /// Sets the catch block to current builder.
  237. /// - Parameter block: Called when an error happens during the image task.
  238. /// - Returns: A `KF.Builder` with changes applied.
  239. public func `catch`(_ block: ((KingfisherError) -> Void)?) -> Self {
  240. self.errorBlock = block
  241. return self
  242. }
  243. }
  244. #if !os(watchOS)
  245. extension KF.Builder {
  246. #if os(iOS) || os(tvOS)
  247. /// Sets a placeholder which is used while retrieving the image.
  248. /// - Parameter placeholder: A placeholder to show while retrieving the image from its source.
  249. /// - Returns: A `KF.Builder` with changes applied.
  250. public func placeholder(_ placeholder: Placeholder?) -> Self {
  251. self.placeholder = placeholder
  252. return self
  253. }
  254. #endif
  255. /// Sets a placeholder image which is used while retrieving the image.
  256. /// - Parameter placeholder: An image to show while retrieving the image from its source.
  257. /// - Returns: A `KF.Builder` with changes applied.
  258. public func placeholder(_ image: KFCrossPlatformImage?) -> Self {
  259. self.placeholder = image
  260. return self
  261. }
  262. }
  263. #endif
  264. extension KF.Builder {
  265. /// Sets the target image cache for this task.
  266. /// - Parameter cache: The target cache is about to be used for the task.
  267. /// - Returns: A `KF.Builder` with changes applied.
  268. ///
  269. /// Kingfisher will use the associated `ImageCache` object when handling related operations,
  270. /// including trying to retrieve the cached images and store the downloaded image to it.
  271. ///
  272. public func targetCache(_ cache: ImageCache) -> Self {
  273. options.targetCache = cache
  274. return self
  275. }
  276. /// Sets the target image cache to store the original downloaded image for this task.
  277. /// - Parameter cache: The target cache is about to be used for storing the original downloaded image from the task.
  278. /// - Returns: A `KF.Builder` with changes applied.
  279. ///
  280. /// The `ImageCache` for storing and retrieving original images. If `originalCache` is
  281. /// contained in the options, it will be preferred for storing and retrieving original images.
  282. /// If there is no `.originalCache` in the options, `.targetCache` will be used to store original images.
  283. ///
  284. /// When using KingfisherManager to download and store an image, if `cacheOriginalImage` is
  285. /// applied in the option, the original image will be stored to this `originalCache`. At the
  286. /// same time, if a requested final image (with processor applied) cannot be found in `targetCache`,
  287. /// Kingfisher will try to search the original image to check whether it is already there. If found,
  288. /// it will be used and applied with the given processor. It is an optimization for not downloading
  289. /// the same image for multiple times.
  290. ///
  291. public func originalCache(_ cache: ImageCache) -> Self {
  292. options.originalCache = cache
  293. return self
  294. }
  295. /// Sets the downloader used to perform the image download task.
  296. /// - Parameter downloader: The downloader which is about to be used for downloading.
  297. /// - Returns: A `KF.Builder` with changes applied.
  298. ///
  299. /// Kingfisher will use the set `ImageDownloader` object to download the requested images.
  300. public func downloader(_ downloader: ImageDownloader) -> Self {
  301. options.downloader = downloader
  302. return self
  303. }
  304. #if os(iOS) || os(tvOS)
  305. /// Sets the transition for the image task.
  306. /// - Parameter transition: The desired transition effect when setting the image to image view.
  307. /// - Returns: A `KF.Builder` with changes applied.
  308. ///
  309. /// Kingfisher will use the `transition` to animate the image in if it is downloaded from web.
  310. /// The transition will not happen when the
  311. /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when
  312. /// the image being retrieved from cache, also call `forceRefresh()` on the returned `KF.Builder`.
  313. public func transition(_ transition: ImageTransition) -> Self {
  314. options.transition = transition
  315. return self
  316. }
  317. /// Sets a fade transition for the image task.
  318. /// - Parameter duration: The duration of the fade transition.
  319. /// - Returns: A `KF.Builder` with changes applied.
  320. ///
  321. /// Kingfisher will use the fade transition to animate the image in if it is downloaded from web.
  322. /// The transition will not happen when the
  323. /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when
  324. /// the image being retrieved from cache, also call `forceRefresh()` on the returned `KF.Builder`.
  325. public func fade(duration: TimeInterval) -> Self {
  326. options.transition = .fade(duration)
  327. return self
  328. }
  329. #endif
  330. /// Sets the download priority for the image task.
  331. /// - Parameter priority: The download priority of image download task.
  332. /// - Returns: A `KF.Builder` with changes applied.
  333. ///
  334. /// The `priority` value will be set as the priority of the image download task. The value for it should be
  335. /// between 0.0~1.0. You can choose a value between `URLSessionTask.defaultPriority`, `URLSessionTask.lowPriority`
  336. /// or `URLSessionTask.highPriority`. If this option not set, the default value (`URLSessionTask.defaultPriority`)
  337. /// will be used.
  338. public func downloadPriority(_ priority: Float) -> Self {
  339. options.downloadPriority = priority
  340. return self
  341. }
  342. /// Sets whether Kingfisher should ignore the cache and try to start a download task for the image source.
  343. /// - Parameter enabled: Enable the force refresh or not.
  344. /// - Returns: A `KF.Builder` with changes applied.
  345. public func forceRefresh(_ enabled: Bool = true) -> Self {
  346. options.forceRefresh = enabled
  347. return self
  348. }
  349. /// Sets whether Kingfisher should try to retrieve the image from memory cache first. If not found, it ignores the
  350. /// disk cache and starts a download task for the image source.
  351. /// - Parameter enabled: Enable the memory-only cache searching or not.
  352. /// - Returns: A `KF.Builder` with changes applied.
  353. ///
  354. /// This is useful when
  355. /// you want to display a changeable image behind the same url at the same app session, while avoiding download
  356. /// it for multiple times.
  357. public func fromMemoryCacheOrRefresh(_ enabled: Bool = true) -> Self {
  358. options.fromMemoryCacheOrRefresh = enabled
  359. return self
  360. }
  361. /// Sets whether the image setting for an image view should happen with transition even when retrieved from cache.
  362. /// - Parameter enabled: Enable the force transition or not.
  363. /// - Returns: A `KF.Builder` with changes applied.
  364. public func forceTransition(_ enabled: Bool = true) -> Self {
  365. options.forceTransition = enabled
  366. return self
  367. }
  368. /// Sets whether the image should only be cached in memory but not in disk.
  369. /// - Parameter enabled: Whether the image should be only cache in memory or not.
  370. /// - Returns: A `KF.Builder` with changes applied.
  371. public func cacheMemoryOnly(_ enabled: Bool = true) -> Self {
  372. options.cacheMemoryOnly = enabled
  373. return self
  374. }
  375. /// Sets whether Kingfisher should wait for caching operation to be completed before calling the
  376. /// `done` or `catch` block.
  377. /// - Parameter enabled: Whether Kingfisher should wait for caching operation.
  378. /// - Returns: A `KF.Builder` with changes applied.
  379. public func waitForCache(_ enabled: Bool = true) -> Self {
  380. options.waitForCache = enabled
  381. return self
  382. }
  383. /// Sets whether Kingfisher should only try to retrieve the image from cache, but not from network.
  384. /// - Parameter enabled: Whether Kingfisher should only try to retrieve the image from cache.
  385. /// - Returns: A `KF.Builder` with changes applied.
  386. ///
  387. /// If the image is not in cache, the image retrieving will fail with the
  388. /// `KingfisherError.cacheError` with `.imageNotExisting` as its reason.
  389. public func onlyFromCache(_ enabled: Bool = true) -> Self {
  390. options.onlyFromCache = enabled
  391. return self
  392. }
  393. /// Sets whether the image should be decoded in a background thread before using.
  394. /// - Parameter enabled: Whether the image should be decoded in a background thread before using.
  395. /// - Returns: A `KF.Builder` with changes applied.
  396. ///
  397. /// Setting to `true` will decode the downloaded image data and do a off-screen rendering to extract pixel
  398. /// information in background. This can speed up display, but will cost more time and memory to prepare the image
  399. /// for using.
  400. public func backgroundDecode(_ enabled: Bool = true) -> Self {
  401. options.backgroundDecode = enabled
  402. return self
  403. }
  404. /// Sets the callback queue which is used as the target queue of dispatch callbacks when retrieving images from
  405. /// cache. If not set, Kingfisher will use main queue for callbacks.
  406. /// - Parameter queue: The target queue which the cache retrieving callback will be invoked on.
  407. /// - Returns: A `KF.Builder` with changes applied.
  408. ///
  409. /// - Note:
  410. /// This option does not affect the callbacks for UI related extension methods. You will always get the
  411. /// callbacks called from main queue.
  412. public func callbackQueue(_ queue: CallbackQueue) -> Self {
  413. options.callbackQueue = queue
  414. return self
  415. }
  416. /// Sets the scale factor value when converting retrieved data to an image.
  417. /// - Parameter factor: The scale factor value.
  418. /// - Returns: A `KF.Builder` with changes applied.
  419. ///
  420. /// Specify the image scale, instead of your screen scale. You may need to set the correct scale when you dealing
  421. /// with 2x or 3x retina images. Otherwise, Kingfisher will convert the data to image object at `scale` 1.0.
  422. ///
  423. public func scaleFactor(_ factor: CGFloat) -> Self {
  424. options.scaleFactor = factor
  425. return self
  426. }
  427. /// Sets whether keeping the existing image of image view while setting another image to it.
  428. /// - Parameter enabled: Whether the existing image should be kept.
  429. /// - Returns: A `KF.Builder` with changes applied.
  430. ///
  431. /// By setting this option, the placeholder image parameter of image view extension method
  432. /// will be ignored and the current image will be kept while loading or downloading the new image.
  433. ///
  434. public func keepCurrentImageWhileLoading(_ enabled: Bool = true) -> Self {
  435. options.keepCurrentImageWhileLoading = enabled
  436. return self
  437. }
  438. /// Sets whether only the first frame from an animated image file should be loaded as a single image.
  439. /// - Parameter enabled: Whether the only the first frame should be loaded.
  440. /// - Returns: A `KF.Builder` with changes applied.
  441. ///
  442. /// Loading an animated images may take too much memory. It will be useful when you want to display a
  443. /// static preview of the first frame from an animated image.
  444. ///
  445. /// This option will be ignored if the target image is not animated image data.
  446. ///
  447. public func onlyLoadFirstFrame(_ enabled: Bool = true) -> Self {
  448. options.onlyLoadFirstFrame = enabled
  449. return self
  450. }
  451. /// Sets whether the original image should be cached even when the original image has been processed by any other
  452. /// `ImageProcessor`s.
  453. /// - Parameter enabled: Whether the original image should be cached.
  454. /// - Returns: A `KF.Builder` with changes applied.
  455. ///
  456. /// If set and an `ImageProcessor` is used, Kingfisher will try to cache both the final result and original
  457. /// image. Kingfisher will have a chance to use the original image when another processor is applied to the same
  458. /// resource, instead of downloading it again. You can use `.originalCache` to specify a cache or the original
  459. /// images if necessary.
  460. ///
  461. /// The original image will be only cached to disk storage.
  462. ///
  463. public func cacheOriginalImage(_ enabled: Bool = true) -> Self {
  464. options.cacheOriginalImage = enabled
  465. return self
  466. }
  467. /// Sets the image that will be used if an image retrieving task fails.
  468. /// - Parameter image: The image that will be used when something goes wrong.
  469. /// - Returns: A `KF.Builder` with changes applied.
  470. ///
  471. /// If set and an image retrieving error occurred Kingfisher will set provided image (or empty)
  472. /// in place of requested one. It's useful when you don't want to show placeholder
  473. /// during loading time but wants to use some default image when requests will be failed.
  474. ///
  475. public func onFailureImage(_ image: KFCrossPlatformImage?) -> Self {
  476. options.onFailureImage = .some(image)
  477. return self
  478. }
  479. /// Sets whether the disk storage loading should happen in the same calling queue.
  480. /// - Parameter enabled: Whether the disk storage loading should happen in the same calling queue.
  481. /// - Returns: A `KF.Builder` with changes applied.
  482. ///
  483. /// By default, disk storage file loading
  484. /// happens in its own queue with an asynchronous dispatch behavior. Although it provides better non-blocking disk
  485. /// loading performance, it also causes a flickering when you reload an image from disk, if the image view already
  486. /// has an image set.
  487. ///
  488. /// Set this options will stop that flickering by keeping all loading in the same queue (typically the UI queue
  489. /// if you are using Kingfisher's extension methods to set an image), with a tradeoff of loading performance.
  490. ///
  491. public func loadDiskFileSynchronously(_ enabled: Bool = true) -> Self {
  492. options.loadDiskFileSynchronously = enabled
  493. return self
  494. }
  495. /// Sets a queue on which the image processing should happen.
  496. /// - Parameter queue: The queue on which the image processing should happen.
  497. /// - Returns: A `KF.Builder` with changes applied.
  498. ///
  499. /// By default, Kingfisher uses a pre-defined serial
  500. /// queue to process images. Use this option to change this behavior. For example, specify a `.mainCurrentOrAsync`
  501. /// to let the image be processed in main queue to prevent a possible flickering (but with a possibility of
  502. /// blocking the UI, especially if the processor needs a lot of time to run).
  503. public func processingQueue(_ queue: CallbackQueue?) -> Self {
  504. options.processingQueue = queue
  505. return self
  506. }
  507. /// Enables progressive image loading with a specified `ImageProgressive` setting to process the
  508. /// progressive JPEG data and display it in a progressive way.
  509. /// - Parameter progressive: The progressive settings which is used while loading.
  510. /// - Returns: A `KF.Builder` with changes applied.
  511. public func progressiveJPEG(_ progressive: ImageProgressive? = .default) -> Self {
  512. options.progressiveJPEG = progressive
  513. return self
  514. }
  515. /// Sets the alternative sources that will be used when loading of the original input `Source` fails.
  516. /// - Parameter sources: The alternative sources will be used.
  517. /// - Returns: A `KF.Builder` with changes applied.
  518. ///
  519. /// Values of the `sources` array will be used to start a new image loading task if the previous task
  520. /// fails due to an error. The image source loading process will stop as soon as a source is loaded successfully.
  521. /// If all `sources` are used but the loading is still failing, an `imageSettingError` with
  522. /// `alternativeSourcesExhausted` as its reason will be given out in the `catch` block.
  523. ///
  524. /// This is useful if you want to implement a fallback solution for setting image.
  525. ///
  526. /// User cancellation will not trigger the alternative source loading.
  527. public func alternativeSources(_ sources: [Source]?) -> Self {
  528. options.alternativeSources = sources
  529. return self
  530. }
  531. /// Sets a retry strategy that will be used when something gets wrong during the image retrieving.
  532. /// - Parameter strategy: The provided strategy to define how the retrying should happen.
  533. /// - Returns: A `KF.Builder` with changes applied.
  534. public func retry(_ strategy: RetryStrategy) -> Self {
  535. options.retryStrategy = strategy
  536. return self
  537. }
  538. /// Sets a retry strategy with a max retry count and retrying interval.
  539. /// - Parameters:
  540. /// - maxCount: The maximum count before the retry stops.
  541. /// - interval: The time interval between each retry attempt.
  542. /// - Returns: A `KF.Builder` with changes applied.
  543. ///
  544. /// This defines the simplest retry strategy, which retry a failing request for several times, with some certain
  545. /// interval between each time. For example, `.retry(maxCount: 3, interval: .second(3))` means attempt for at most
  546. /// three times, and wait for 3 seconds if a previous retry attempt fails, then start a new attempt.
  547. public func retry(maxCount: Int, interval: DelayRetryStrategy.Interval = .seconds(3)) -> Self {
  548. let strategy = DelayRetryStrategy(maxRetryCount: maxCount, retryInterval: interval)
  549. options.retryStrategy = strategy
  550. return self
  551. }
  552. }
  553. // MARK: - Request Modifier
  554. extension KF.Builder {
  555. /// Sets an `ImageDownloadRequestModifier` to change the image download request before it being sent.
  556. /// - Parameter modifier: The modifier will be used to change the request before it being sent.
  557. /// - Returns: A `KF.Builder` with changes applied.
  558. ///
  559. /// This is the last chance you can modify the image download request. You can modify the request for some
  560. /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping.
  561. ///
  562. public func requestModifier(_ modifier: ImageDownloadRequestModifier) -> Self {
  563. options.requestModifier = modifier
  564. return self
  565. }
  566. /// Sets a block to change the image download request before it being sent.
  567. /// - Parameter modifyBlock: The modifying block will be called to change the request before it being sent.
  568. /// - Returns: A `KF.Builder` with changes applied.
  569. ///
  570. /// This is the last chance you can modify the image download request. You can modify the request for some
  571. /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping.
  572. ///
  573. public func requestModifier(_ modifyBlock: @escaping (inout URLRequest) -> Void) -> Self {
  574. options.requestModifier = AnyModifier { r -> URLRequest? in
  575. var request = r
  576. modifyBlock(&request)
  577. return request
  578. }
  579. return self
  580. }
  581. }
  582. // MARK: - Redirect Handler
  583. extension KF {
  584. /// Represents the detail information when a task redirect happens. It is wrapping necessary information for a
  585. /// `ImageDownloadRedirectHandler`. See that protocol for more information.
  586. public struct RedirectPayload {
  587. /// The related session data task when the redirect happens. It is
  588. /// the current `SessionDataTask` which triggers this redirect.
  589. public let task: SessionDataTask
  590. /// The response received during redirection.
  591. public let response: HTTPURLResponse
  592. /// The request for redirection which can be modified.
  593. public let newRequest: URLRequest
  594. /// A closure for being called with modified request.
  595. public let completionHandler: (URLRequest?) -> Void
  596. }
  597. }
  598. extension KF.Builder {
  599. /// The `ImageDownloadRedirectHandler` argument will be used to change the request before redirection.
  600. /// This is the possibility you can modify the image download request during redirect. You can modify the request for
  601. /// some customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url
  602. /// mapping.
  603. /// The original redirection request will be sent without any modification by default.
  604. /// - Parameter handler: The handler will be used for redirection.
  605. /// - Returns: A `KF.Builder` with changes applied.
  606. public func redirectHandler(_ handler: ImageDownloadRedirectHandler) -> Self {
  607. options.redirectHandler = handler
  608. return self
  609. }
  610. /// The `block` will be used to change the request before redirection.
  611. /// This is the possibility you can modify the image download request during redirect. You can modify the request for
  612. /// some customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url
  613. /// mapping.
  614. /// The original redirection request will be sent without any modification by default.
  615. /// - Parameter block: The block will be used for redirection.
  616. /// - Returns: A `KF.Builder` with changes applied.
  617. public func redirectHandler(_ block: @escaping (KF.RedirectPayload) -> Void) -> Self {
  618. let redirectHandler = AnyRedirectHandler { (task, response, request, handler) in
  619. let payload = KF.RedirectPayload(
  620. task: task, response: response, newRequest: request, completionHandler: handler
  621. )
  622. block(payload)
  623. }
  624. options.redirectHandler = redirectHandler
  625. return self
  626. }
  627. }
  628. // MARK: - Processor
  629. extension KF.Builder {
  630. /// Sets an image processor for the image task. It replaces the current image processor settings.
  631. ///
  632. /// - Parameter processor: The processor you want to use to process the image after it is downloaded.
  633. /// - Returns: A `KF.Builder` with changes applied.
  634. ///
  635. /// - Note:
  636. /// To append a processor to current ones instead of replacing them all, use `appendProcessor(_:)`.
  637. public func setProcessor(_ processor: ImageProcessor) -> Self {
  638. options.processor = processor
  639. return self
  640. }
  641. /// Sets an array of image processors for the image task. It replaces the current image processor settings.
  642. /// - Parameter processors: An array of processors. The processors inside this array will be concatenated one by one
  643. /// to form a processor pipeline.
  644. /// - Returns: A `KF.Builder` with changes applied.
  645. ///
  646. /// - Note:
  647. /// To append processors to current ones instead of replacing them all, concatenate them by `|>`, then use
  648. /// `appendProcessor(_:)`.
  649. public func setProcessors(_ processors: [ImageProcessor]) -> Self {
  650. switch processors.count {
  651. case 0:
  652. options.processor = DefaultImageProcessor.default
  653. case 1...:
  654. options.processor = processors.dropFirst().reduce(processors[0]) { $0 |> $1 }
  655. default:
  656. assertionFailure("Never happen")
  657. }
  658. return self
  659. }
  660. /// Appends a processor to the current set processors.
  661. /// - Parameter processor: The processor which will be appended to current processor settings.
  662. /// - Returns: A `KF.Builder` with changes applied.
  663. public func appendProcessor(_ processor: ImageProcessor) -> Self {
  664. options.processor = options.processor |> processor
  665. return self
  666. }
  667. /// Appends a `RoundCornerImageProcessor` to current processors.
  668. /// - Parameters:
  669. /// - radius: The radius will be applied in processing. Specify a certain point value with `.point`, or a fraction
  670. /// of the target image with `.widthFraction`. or `.heightFraction`. For example, given a square image
  671. /// with width and height equals, `.widthFraction(0.5)` means use half of the length of size and makes
  672. /// the final image a round one.
  673. /// - targetSize: Target size of output image should be. If `nil`, the image will keep its original size after processing.
  674. /// - corners: The target corners which will be applied rounding.
  675. /// - backgroundColor: Background color of the output image. If `nil`, it will use a transparent background.
  676. /// - Returns: A `KF.Builder` with changes applied.
  677. public func roundCorner(
  678. radius: RoundCornerImageProcessor.Radius,
  679. targetSize: CGSize? = nil,
  680. roundingCorners corners: RectCorner = .all,
  681. backgroundColor: KFCrossPlatformColor? = nil
  682. ) -> Self
  683. {
  684. let processor = RoundCornerImageProcessor(
  685. radius: radius,
  686. targetSize: targetSize,
  687. roundingCorners: corners,
  688. backgroundColor: backgroundColor
  689. )
  690. return appendProcessor(processor)
  691. }
  692. /// Appends a `BlurImageProcessor` to current processors.
  693. /// - Parameter radius: Blur radius for the simulated Gaussian blur.
  694. /// - Returns: A `KF.Builder` with changes applied.
  695. public func blur(radius: CGFloat) -> Self {
  696. appendProcessor(
  697. BlurImageProcessor(blurRadius: radius)
  698. )
  699. }
  700. /// Appends a `OverlayImageProcessor` to current processors.
  701. /// - Parameters:
  702. /// - color: Overlay color will be used to overlay the input image.
  703. /// - fraction: Fraction will be used when overlay the color to image.
  704. /// - Returns: A `KF.Builder` with changes applied.
  705. public func overlay(color: KFCrossPlatformColor, fraction: CGFloat = 0.5) -> Self {
  706. appendProcessor(
  707. OverlayImageProcessor(overlay: color, fraction: fraction)
  708. )
  709. }
  710. /// Appends a `TintImageProcessor` to current processors.
  711. /// - Parameter color: Tint color will be used to tint the input image.
  712. /// - Returns: A `KF.Builder` with changes applied.
  713. public func tint(color: KFCrossPlatformColor) -> Self {
  714. appendProcessor(
  715. TintImageProcessor(tint: color)
  716. )
  717. }
  718. /// Appends a `BlackWhiteProcessor` to current processors.
  719. /// - Returns: A `KF.Builder` with changes applied.
  720. public func blackWhite() -> Self {
  721. appendProcessor(
  722. BlackWhiteProcessor()
  723. )
  724. }
  725. /// Appends a `CroppingImageProcessor` to current processors.
  726. /// - Parameters:
  727. /// - size: Target size of output image should be.
  728. /// - anchor: Anchor point from which the output size should be calculate. The anchor point is consisted by two
  729. /// values between 0.0 and 1.0. It indicates a related point in current image.
  730. /// See `CroppingImageProcessor.init(size:anchor:)` for more.
  731. /// - Returns: A `KF.Builder` with changes applied.
  732. public func cropping(size: CGSize, anchor: CGPoint = .init(x: 0.5, y: 0.5)) -> Self {
  733. appendProcessor(
  734. CroppingImageProcessor(size: size, anchor: anchor)
  735. )
  736. }
  737. /// Appends a `DownsamplingImageProcessor` to current processors.
  738. ///
  739. /// Compared to `ResizingImageProcessor`, the `DownsamplingImageProcessor` does not render the original images and
  740. /// then resize it. Instead, it downsamples the input data directly to a thumbnail image. So it is a more efficient
  741. /// than `ResizingImageProcessor`. Prefer to use `DownsamplingImageProcessor` as possible
  742. /// as you can than the `ResizingImageProcessor`.
  743. ///
  744. /// Only CG-based images are supported. Animated images (like GIF) is not supported.
  745. ///
  746. /// - Parameter size: Target size of output image should be. It should be smaller than the size of input image.
  747. /// If it is larger, the result image will be the same size of input data without downsampling.
  748. /// - Returns: A `KF.Builder` with changes applied.
  749. public func downsampling(size: CGSize) -> Self {
  750. let processor = DownsamplingImageProcessor(size: size)
  751. if options.processor == DefaultImageProcessor.default {
  752. return setProcessor(processor)
  753. } else {
  754. return appendProcessor(processor)
  755. }
  756. }
  757. /// Appends a `ResizingImageProcessor` to current processors.
  758. ///
  759. /// If you need to resize a data represented image to a smaller size, use `DownsamplingImageProcessor`
  760. /// instead, which is more efficient and uses less memory.
  761. ///
  762. /// - Parameters:
  763. /// - referenceSize: The reference size for resizing operation in point.
  764. /// - mode: Target content mode of output image should be. Default is `.none`.
  765. /// - Returns: A `KF.Builder` with changes applied.
  766. public func resizing(referenceSize: CGSize, mode: ContentMode = .none) -> Self {
  767. appendProcessor(
  768. ResizingImageProcessor(referenceSize: referenceSize, mode: mode)
  769. )
  770. }
  771. }
  772. // MARK: - Cache Serializer
  773. extension KF.Builder {
  774. /// Uses a given `CacheSerializer` to convert some data to an image object for retrieving from disk cache or vice
  775. /// versa for storing to disk cache.
  776. /// - Parameter cacheSerializer: The `CacheSerializer` which will be used.
  777. /// - Returns: A `KF.Builder` with changes applied.
  778. public func serialize(by cacheSerializer: CacheSerializer) -> Self {
  779. options.cacheSerializer = cacheSerializer
  780. return self
  781. }
  782. /// Uses a given format to serializer the image data to disk. It converts the image object to the give data format.
  783. /// - Parameters:
  784. /// - format: The desired data encoding format when store the image on disk.
  785. /// - jpegCompressionQuality: If the format is `.JPEG`, it specify the compression quality when converting the
  786. /// image to a JPEG data. Otherwise, it is ignored.
  787. /// - Returns: A `KF.Builder` with changes applied.
  788. public func serialize(as format: ImageFormat, jpegCompressionQuality: CGFloat? = nil) -> Self {
  789. let cacheSerializer: FormatIndicatedCacheSerializer
  790. switch format {
  791. case .JPEG:
  792. cacheSerializer = .jpeg(compressionQuality: jpegCompressionQuality ?? 1.0)
  793. case .PNG:
  794. cacheSerializer = .png
  795. case .GIF:
  796. cacheSerializer = .gif
  797. case .unknown:
  798. cacheSerializer = .png
  799. }
  800. options.cacheSerializer = cacheSerializer
  801. return self
  802. }
  803. }
  804. // MARK: - Image Modifier
  805. extension KF.Builder {
  806. /// Sets an `ImageModifier` to the image task. Use this to modify the fetched image object properties if needed.
  807. ///
  808. /// If the image was fetched directly from the downloader, the modifier will run directly after the
  809. /// `ImageProcessor`. If the image is being fetched from a cache, the modifier will run after the `CacheSerializer`.
  810. /// - Parameter modifier: The `ImageModifier` which will be used to modify the image object.
  811. /// - Returns: A `KF.Builder` with changes applied.
  812. public func imageModifier(_ modifier: ImageModifier?) -> Self {
  813. options.imageModifier = modifier
  814. return self
  815. }
  816. /// Sets a block to modify the image object. Use this to modify the fetched image object properties if needed.
  817. ///
  818. /// If the image was fetched directly from the downloader, the modifier block will run directly after the
  819. /// `ImageProcessor`. If the image is being fetched from a cache, the modifier will run after the `CacheSerializer`.
  820. ///
  821. /// - Parameter block: The block which is used to modify the image object.
  822. /// - Returns: A `KF.Builder` with changes applied.
  823. public func imageModifier(_ block: @escaping (inout KFCrossPlatformImage) throws -> Void) -> Self {
  824. let modifier = AnyImageModifier { image -> KFCrossPlatformImage in
  825. var image = image
  826. try block(&image)
  827. return image
  828. }
  829. options.imageModifier = modifier
  830. return self
  831. }
  832. }
  833. // MARK: - Cache Expiration
  834. extension KF.Builder {
  835. /// Sets the expiration setting for memory cache of this image task.
  836. ///
  837. /// By default, the underlying `MemoryStorage.Backend` uses the
  838. /// expiration in its config for all items. If set, the `MemoryStorage.Backend` will use this value to overwrite
  839. /// the config setting for this caching item.
  840. ///
  841. /// - Parameter expiration: The expiration setting used in cache storage.
  842. /// - Returns: A `KF.Builder` with changes applied.
  843. public func memoryCacheExpiration(_ expiration: StorageExpiration?) -> Self {
  844. options.memoryCacheExpiration = expiration
  845. return self
  846. }
  847. /// Sets the expiration extending setting for memory cache. The item expiration time will be incremented by this
  848. /// value after access.
  849. ///
  850. /// By default, the underlying `MemoryStorage.Backend` uses the initial cache expiration as extending
  851. /// value: .cacheTime.
  852. ///
  853. /// To disable extending option at all, sets `.none` to it.
  854. ///
  855. /// - Parameter extending: The expiration extending setting used in cache storage.
  856. /// - Returns: A `KF.Builder` with changes applied.
  857. public func memoryCacheAccessExtending(_ extending: ExpirationExtending) -> Self {
  858. options.memoryCacheAccessExtendingExpiration = extending
  859. return self
  860. }
  861. /// Sets the expiration setting for disk cache of this image task.
  862. ///
  863. /// By default, the underlying `DiskStorage.Backend` uses the expiration in its config for all items. If set,
  864. /// the `DiskStorage.Backend` will use this value to overwrite the config setting for this caching item.
  865. ///
  866. /// - Parameter expiration: The expiration setting used in cache storage.
  867. /// - Returns: A `KF.Builder` with changes applied.
  868. public func diskCacheExpiration(_ expiration: StorageExpiration?) -> Self {
  869. options.diskCacheExpiration = expiration
  870. return self
  871. }
  872. /// Sets the expiration extending setting for disk cache. The item expiration time will be incremented by this
  873. /// value after access.
  874. ///
  875. /// By default, the underlying `DiskStorage.Backend` uses the initial cache expiration as extending
  876. /// value: .cacheTime.
  877. ///
  878. /// To disable extending option at all, sets `.none` to it.
  879. ///
  880. /// - Parameter extending: The expiration extending setting used in cache storage.
  881. /// - Returns: A `KF.Builder` with changes applied.
  882. public func diskCacheAccessExtending(_ extending: ExpirationExtending) -> Self {
  883. options.diskCacheAccessExtendingExpiration = extending
  884. return self
  885. }
  886. }