123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- //
- // KF.swift
- // Kingfisher
- //
- // Created by onevcat on 2020/09/21.
- //
- // Copyright (c) 2020 Wei Wang <onevcat@gmail.com>
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- #if canImport(UIKit)
- import UIKit
- #endif
- #if canImport(CarPlay) && !targetEnvironment(macCatalyst)
- import CarPlay
- #endif
- #if canImport(AppKit) && !targetEnvironment(macCatalyst)
- import AppKit
- #endif
- #if canImport(WatchKit)
- import WatchKit
- #endif
- #if canImport(TVUIKit)
- import TVUIKit
- #endif
- /// A helper type to create image setting tasks in a builder pattern.
- /// Use methods in this type to create a `KF.Builder` instance and configure image tasks there.
- public enum KF {
- /// Creates a builder for a given `Source`.
- /// - Parameter source: The `Source` object defines data information from network or a data provider.
- /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
- /// to start the image loading.
- public static func source(_ source: Source?) -> KF.Builder {
- Builder(source: source)
- }
- /// Creates a builder for a given `Resource`.
- /// - Parameter resource: The `Resource` object defines data information like key or URL.
- /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
- /// to start the image loading.
- public static func resource(_ resource: Resource?) -> KF.Builder {
- source(resource?.convertToSource())
- }
- /// Creates a builder for a given `URL` and an optional cache key.
- /// - Parameters:
- /// - url: The URL where the image should be downloaded.
- /// - cacheKey: The key used to store the downloaded image in cache.
- /// If `nil`, the `absoluteString` of `url` is used as the cache key.
- /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
- /// to start the image loading.
- public static func url(_ url: URL?, cacheKey: String? = nil) -> KF.Builder {
- source(url?.convertToSource(overrideCacheKey: cacheKey))
- }
- /// Creates a builder for a given `ImageDataProvider`.
- /// - Parameter provider: The `ImageDataProvider` object contains information about the data.
- /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
- /// to start the image loading.
- public static func dataProvider(_ provider: ImageDataProvider?) -> KF.Builder {
- source(provider?.convertToSource())
- }
- /// Creates a builder for some given raw data and a cache key.
- /// - Parameters:
- /// - data: The data object from which the image should be created.
- /// - cacheKey: The key used to store the downloaded image in cache.
- /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
- /// to start the image loading.
- public static func data(_ data: Data?, cacheKey: String) -> KF.Builder {
- if let data = data {
- return dataProvider(RawImageDataProvider(data: data, cacheKey: cacheKey))
- } else {
- return dataProvider(nil)
- }
- }
- }
- extension KF {
- /// A builder class to configure an image retrieving task and set it to a holder view or component.
- public class Builder {
- private let source: Source?
- #if os(watchOS)
- private var placeholder: KFCrossPlatformImage?
- #else
- private var placeholder: Placeholder?
- #endif
- public var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions)
- public let onFailureDelegate = Delegate<KingfisherError, Void>()
- public let onSuccessDelegate = Delegate<RetrieveImageResult, Void>()
- public let onProgressDelegate = Delegate<(Int64, Int64), Void>()
- init(source: Source?) {
- self.source = source
- }
- private var resultHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)? {
- {
- switch $0 {
- case .success(let result):
- self.onSuccessDelegate(result)
- case .failure(let error):
- self.onFailureDelegate(error)
- }
- }
- }
- private var progressBlock: DownloadProgressBlock {
- { self.onProgressDelegate(($0, $1)) }
- }
- }
- }
- extension KF.Builder {
- #if !os(watchOS)
- /// Builds the image task request and sets it to an image view.
- /// - Parameter imageView: The image view which loads the task and should be set with the image.
- /// - Returns: A task represents the image downloading, if initialized.
- /// This value is `nil` if the image is being loaded from cache.
- @discardableResult
- public func set(to imageView: KFCrossPlatformImageView) -> DownloadTask? {
- imageView.kf.setImage(
- with: source,
- placeholder: placeholder,
- parsedOptions: options,
- progressBlock: progressBlock,
- completionHandler: resultHandler
- )
- }
- /// Builds the image task request and sets it to an `NSTextAttachment` object.
- /// - Parameters:
- /// - attachment: The text attachment object which loads the task and should be set with the image.
- /// - attributedView: The owner of the attributed string which this `NSTextAttachment` is added.
- /// - Returns: A task represents the image downloading, if initialized.
- /// This value is `nil` if the image is being loaded from cache.
- @discardableResult
- public func set(to attachment: NSTextAttachment, attributedView: @autoclosure @escaping () -> KFCrossPlatformView) -> DownloadTask? {
- let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
- return attachment.kf.setImage(
- with: source,
- attributedView: attributedView,
- placeholder: placeholderImage,
- parsedOptions: options,
- progressBlock: progressBlock,
- completionHandler: resultHandler
- )
- }
- #if canImport(UIKit)
- /// Builds the image task request and sets it to a button.
- /// - Parameters:
- /// - button: The button which loads the task and should be set with the image.
- /// - state: The button state to which the image should be set.
- /// - Returns: A task represents the image downloading, if initialized.
- /// This value is `nil` if the image is being loaded from cache.
- @discardableResult
- public func set(to button: UIButton, for state: UIControl.State) -> DownloadTask? {
- let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
- return button.kf.setImage(
- with: source,
- for: state,
- placeholder: placeholderImage,
- parsedOptions: options,
- progressBlock: progressBlock,
- completionHandler: resultHandler
- )
- }
- /// Builds the image task request and sets it to the background image for a button.
- /// - Parameters:
- /// - button: The button which loads the task and should be set with the image.
- /// - state: The button state to which the image should be set.
- /// - Returns: A task represents the image downloading, if initialized.
- /// This value is `nil` if the image is being loaded from cache.
- @discardableResult
- public func setBackground(to button: UIButton, for state: UIControl.State) -> DownloadTask? {
- let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
- return button.kf.setBackgroundImage(
- with: source,
- for: state,
- placeholder: placeholderImage,
- parsedOptions: options,
- progressBlock: progressBlock,
- completionHandler: resultHandler
- )
- }
- #endif // end of canImport(UIKit)
-
- #if canImport(CarPlay) && !targetEnvironment(macCatalyst)
-
- /// Builds the image task request and sets it to the image for a list item.
- /// - Parameters:
- /// - listItem: The list item which loads the task and should be set with the image.
- /// - Returns: A task represents the image downloading, if initialized.
- /// This value is `nil` if the image is being loaded from cache.
- @available(iOS 14.0, *)
- @discardableResult
- public func set(to listItem: CPListItem) -> DownloadTask? {
- let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
- return listItem.kf.setImage(
- with: source,
- placeholder: placeholderImage,
- parsedOptions: options,
- progressBlock: progressBlock,
- completionHandler: resultHandler
- )
-
- }
-
- #endif
- #if canImport(AppKit) && !targetEnvironment(macCatalyst)
- /// Builds the image task request and sets it to a button.
- /// - Parameter button: The button which loads the task and should be set with the image.
- /// - Returns: A task represents the image downloading, if initialized.
- /// This value is `nil` if the image is being loaded from cache.
- @discardableResult
- public func set(to button: NSButton) -> DownloadTask? {
- let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
- return button.kf.setImage(
- with: source,
- placeholder: placeholderImage,
- parsedOptions: options,
- progressBlock: progressBlock,
- completionHandler: resultHandler
- )
- }
- /// Builds the image task request and sets it to the alternative image for a button.
- /// - Parameter button: The button which loads the task and should be set with the image.
- /// - Returns: A task represents the image downloading, if initialized.
- /// This value is `nil` if the image is being loaded from cache.
- @discardableResult
- public func setAlternative(to button: NSButton) -> DownloadTask? {
- let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
- return button.kf.setAlternateImage(
- with: source,
- placeholder: placeholderImage,
- parsedOptions: options,
- progressBlock: progressBlock,
- completionHandler: resultHandler
- )
- }
- #endif // end of canImport(AppKit)
- #endif // end of !os(watchOS)
- #if canImport(WatchKit)
- /// Builds the image task request and sets it to a `WKInterfaceImage` object.
- /// - Parameter interfaceImage: The watch interface image which loads the task and should be set with the image.
- /// - Returns: A task represents the image downloading, if initialized.
- /// This value is `nil` if the image is being loaded from cache.
- @discardableResult
- public func set(to interfaceImage: WKInterfaceImage) -> DownloadTask? {
- return interfaceImage.kf.setImage(
- with: source,
- placeholder: placeholder,
- parsedOptions: options,
- progressBlock: progressBlock,
- completionHandler: resultHandler
- )
- }
- #endif // end of canImport(WatchKit)
- #if canImport(TVUIKit)
- /// Builds the image task request and sets it to a TV monogram view.
- /// - Parameter monogramView: The monogram view which loads the task and should be set with the image.
- /// - Returns: A task represents the image downloading, if initialized.
- /// This value is `nil` if the image is being loaded from cache.
- @available(tvOS 12.0, *)
- @discardableResult
- public func set(to monogramView: TVMonogramView) -> DownloadTask? {
- let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
- return monogramView.kf.setImage(
- with: source,
- placeholder: placeholderImage,
- parsedOptions: options,
- progressBlock: progressBlock,
- completionHandler: resultHandler
- )
- }
- #endif // end of canImport(TVUIKit)
- }
- #if !os(watchOS)
- extension KF.Builder {
- #if os(iOS) || os(tvOS) || os(visionOS)
- /// Sets a placeholder which is used while retrieving the image.
- /// - Parameter placeholder: A placeholder to show while retrieving the image from its source.
- /// - Returns: A `KF.Builder` with changes applied.
- public func placeholder(_ placeholder: Placeholder?) -> Self {
- self.placeholder = placeholder
- return self
- }
- #endif
- /// Sets a placeholder image which is used while retrieving the image.
- /// - Parameter placeholder: An image to show while retrieving the image from its source.
- /// - Returns: A `KF.Builder` with changes applied.
- public func placeholder(_ image: KFCrossPlatformImage?) -> Self {
- self.placeholder = image
- return self
- }
- }
- #endif
- extension KF.Builder {
- #if os(iOS) || os(tvOS) || os(visionOS)
- /// Sets the transition for the image task.
- /// - Parameter transition: The desired transition effect when setting the image to image view.
- /// - Returns: A `KF.Builder` with changes applied.
- ///
- /// Kingfisher will use the `transition` to animate the image in if it is downloaded from web.
- /// The transition will not happen when the
- /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when
- /// the image being retrieved from cache, also call `forceRefresh()` on the returned `KF.Builder`.
- public func transition(_ transition: ImageTransition) -> Self {
- options.transition = transition
- return self
- }
- /// Sets a fade transition for the image task.
- /// - Parameter duration: The duration of the fade transition.
- /// - Returns: A `KF.Builder` with changes applied.
- ///
- /// Kingfisher will use the fade transition to animate the image in if it is downloaded from web.
- /// The transition will not happen when the
- /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when
- /// the image being retrieved from cache, also call `forceRefresh()` on the returned `KF.Builder`.
- public func fade(duration: TimeInterval) -> Self {
- options.transition = .fade(duration)
- return self
- }
- #endif
- /// Sets whether keeping the existing image of image view while setting another image to it.
- /// - Parameter enabled: Whether the existing image should be kept.
- /// - Returns: A `KF.Builder` with changes applied.
- ///
- /// By setting this option, the placeholder image parameter of image view extension method
- /// will be ignored and the current image will be kept while loading or downloading the new image.
- ///
- public func keepCurrentImageWhileLoading(_ enabled: Bool = true) -> Self {
- options.keepCurrentImageWhileLoading = enabled
- return self
- }
- /// Sets whether only the first frame from an animated image file should be loaded as a single image.
- /// - Parameter enabled: Whether the only the first frame should be loaded.
- /// - Returns: A `KF.Builder` with changes applied.
- ///
- /// Loading an animated images may take too much memory. It will be useful when you want to display a
- /// static preview of the first frame from an animated image.
- ///
- /// This option will be ignored if the target image is not animated image data.
- ///
- public func onlyLoadFirstFrame(_ enabled: Bool = true) -> Self {
- options.onlyLoadFirstFrame = enabled
- return self
- }
- /// Enables progressive image loading with a specified `ImageProgressive` setting to process the
- /// progressive JPEG data and display it in a progressive way.
- /// - Parameter progressive: The progressive settings which is used while loading.
- /// - Returns: A `KF.Builder` with changes applied.
- public func progressiveJPEG(_ progressive: ImageProgressive? = .init()) -> Self {
- options.progressiveJPEG = progressive
- return self
- }
- }
- // MARK: - Deprecated
- extension KF.Builder {
- /// Starts the loading process of `self` immediately.
- ///
- /// By default, a `KFImage` will not load its source until the `onAppear` is called. This is a lazily loading
- /// behavior and provides better performance. However, when you refresh the view, the lazy loading also causes a
- /// flickering since the loading does not happen immediately. Call this method if you want to start the load at once
- /// could help avoiding the flickering, with some performance trade-off.
- ///
- /// - Deprecated: This is not necessary anymore since `@StateObject` is used for holding the image data.
- /// It does nothing now and please just remove it.
- ///
- /// - Returns: The `Self` value with changes applied.
- @available(*, deprecated, message: "This is not necessary anymore since `@StateObject` is used. It does nothing now and please just remove it.")
- public func loadImmediately(_ start: Bool = true) -> Self {
- return self
- }
- }
- // MARK: - Redirect Handler
- extension KF {
- /// Represents the detail information when a task redirect happens. It is wrapping necessary information for a
- /// `ImageDownloadRedirectHandler`. See that protocol for more information.
- public struct RedirectPayload {
- /// The related session data task when the redirect happens. It is
- /// the current `SessionDataTask` which triggers this redirect.
- public let task: SessionDataTask
- /// The response received during redirection.
- public let response: HTTPURLResponse
- /// The request for redirection which can be modified.
- public let newRequest: URLRequest
- /// A closure for being called with modified request.
- public let completionHandler: (URLRequest?) -> Void
- }
- }
|