|
|
@@ -29,52 +29,11 @@ import Combine
|
|
|
import SwiftUI
|
|
|
|
|
|
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
|
|
-extension Image {
|
|
|
- // Creates an Image with either UIImage or NSImage.
|
|
|
- init(crossPlatformImage: KFCrossPlatformImage) {
|
|
|
- #if canImport(UIKit)
|
|
|
- self.init(uiImage: crossPlatformImage)
|
|
|
- #elseif canImport(AppKit)
|
|
|
- self.init(nsImage: crossPlatformImage)
|
|
|
- #endif
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
|
|
-public struct KFImage: View {
|
|
|
+public struct KFImage: KFImageProtocol {
|
|
|
+
|
|
|
+ public typealias HoldingView = Image
|
|
|
|
|
|
- var context: Context
|
|
|
-
|
|
|
- /// Creates a Kingfisher compatible image view to load image from the given `Source`.
|
|
|
- /// - Parameter source: The image `Source` defining where to load the target image.
|
|
|
- /// - Parameter options: The options should be applied when loading the image.
|
|
|
- /// Some UIKit related options (such as `ImageTransition.flip`) are not supported.
|
|
|
- /// - Parameter isLoaded: Whether the image is loaded or not. This provides a way to inspect the internal loading
|
|
|
- /// state. `true` if the image is loaded successfully. Otherwise, `false`. Do not set the
|
|
|
- /// wrapped value from outside.
|
|
|
- /// - Deprecated: Some options are not available in SwiftUI yet. Use `KFImage(source:isLoaded:)` to create a
|
|
|
- /// `KFImage` and configure the options through modifier instead. See methods of `KFOptionSetter`
|
|
|
- /// for more.
|
|
|
- @available(*, deprecated, message: "Some options are not available in SwiftUI yet. Use `KFImage(source:isLoaded:)` to create a `KFImage` and configure the options through modifier instead.")
|
|
|
- public init(source: Source?, options: KingfisherOptionsInfo? = nil, isLoaded: Binding<Bool> = .constant(false)) {
|
|
|
- let binder = KFImage.ImageBinder(source: source, options: options, isLoaded: isLoaded)
|
|
|
- self.init(binder: binder)
|
|
|
- }
|
|
|
-
|
|
|
- /// Creates a Kingfisher compatible image view to load image from the given `URL`.
|
|
|
- /// - Parameter url: The image URL from where to load the target image.
|
|
|
- /// - Parameter options: The options should be applied when loading the image.
|
|
|
- /// Some UIKit related options (such as `ImageTransition.flip`) are not supported.
|
|
|
- /// - Parameter isLoaded: Whether the image is loaded or not. This provides a way to inspect the internal loading
|
|
|
- /// state. `true` if the image is loaded successfully. Otherwise, `false`. Do not set the
|
|
|
- /// wrapped value from outside.
|
|
|
- /// - Deprecated: Some options are not available in SwiftUI yet. Use `KFImage(_:isLoaded:)` to create a
|
|
|
- /// `KFImage` and configure the options through modifier instead. See methods of `KFOptionSetter`
|
|
|
- /// for more.
|
|
|
- @available(*, deprecated, message: "Some options are not available in SwiftUI yet. Use `KFImage(_:isLoaded:)` to create a `KFImage` and configure the options through modifier instead.")
|
|
|
- init(_ url: URL?, options: KingfisherOptionsInfo? = nil, isLoaded: Binding<Bool> = .constant(false)) {
|
|
|
- self.init(source: url?.convertToSource(), options: options, isLoaded: isLoaded)
|
|
|
- }
|
|
|
+ public internal (set) var context: Context<HoldingView>
|
|
|
|
|
|
/// Creates a Kingfisher compatible image view to load image from the given `Source`.
|
|
|
/// - Parameters:
|
|
|
@@ -98,35 +57,15 @@ public struct KFImage: View {
|
|
|
}
|
|
|
|
|
|
init(binder: ImageBinder) {
|
|
|
- self.context = Context(binder: binder)
|
|
|
- }
|
|
|
-
|
|
|
- public var body: some View {
|
|
|
- KFImageRenderer(context)
|
|
|
- .id(context.binder)
|
|
|
- }
|
|
|
-
|
|
|
- /// 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.
|
|
|
- ///
|
|
|
- /// - Returns: The `Self` value with changes applied.
|
|
|
- public func loadImmediately(_ start: Bool = true) -> KFImage {
|
|
|
- if start {
|
|
|
- context.binder.start()
|
|
|
- }
|
|
|
- return self
|
|
|
+ self.context = Context<HoldingView>(binder: binder)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
|
|
extension KFImage {
|
|
|
- struct Context {
|
|
|
+ public struct Context<HoldingView: KFImageHoldingView> {
|
|
|
var binder: ImageBinder
|
|
|
- var configurations: [(Image) -> Image] = []
|
|
|
+ var configurations: [(HoldingView) -> HoldingView] = []
|
|
|
var cancelOnDisappear: Bool = false
|
|
|
var placeholder: AnyView? = nil
|
|
|
|
|
|
@@ -136,110 +75,6 @@ extension KFImage {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/// A Kingfisher compatible SwiftUI `View` to load an image from a `Source`.
|
|
|
-/// Declaring a `KFImage` in a `View`'s body to trigger loading from the given `Source`.
|
|
|
-@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
|
|
-struct KFImageRenderer: View {
|
|
|
-
|
|
|
- /// An image binder that manages loading and cancelling image related task.
|
|
|
- @ObservedObject var binder: KFImage.ImageBinder
|
|
|
-
|
|
|
- // Acts as a placeholder when loading an image.
|
|
|
- var placeholder: AnyView?
|
|
|
-
|
|
|
- // Whether the download task should be cancelled when the view disappears.
|
|
|
- let cancelOnDisappear: Bool
|
|
|
-
|
|
|
- // Configurations should be performed on the image.
|
|
|
- let configurations: [(Image) -> Image]
|
|
|
-
|
|
|
- init(_ context: KFImage.Context) {
|
|
|
- self.binder = context.binder
|
|
|
- self.configurations = context.configurations
|
|
|
- self.placeholder = context.placeholder
|
|
|
- self.cancelOnDisappear = context.cancelOnDisappear
|
|
|
- }
|
|
|
-
|
|
|
- /// Declares the content and behavior of this view.
|
|
|
- @ViewBuilder
|
|
|
- var body: some View {
|
|
|
- if let image = binder.loadedImage {
|
|
|
- configurations
|
|
|
- .reduce(imageFromResult(image)) {
|
|
|
- current, config in config(current)
|
|
|
- }
|
|
|
- .opacity(binder.loaded ? 1.0 : 0.0)
|
|
|
- } else {
|
|
|
- Group {
|
|
|
- if placeholder != nil {
|
|
|
- placeholder
|
|
|
- } else {
|
|
|
- Color.clear
|
|
|
- }
|
|
|
- }
|
|
|
- .onAppear { [weak binder = self.binder] in
|
|
|
- guard let binder = binder else {
|
|
|
- return
|
|
|
- }
|
|
|
- if !binder.loadingOrSucceeded {
|
|
|
- binder.start()
|
|
|
- }
|
|
|
- }
|
|
|
- .onDisappear { [weak binder = self.binder] in
|
|
|
- guard let binder = binder else {
|
|
|
- return
|
|
|
- }
|
|
|
- if self.cancelOnDisappear {
|
|
|
- binder.cancel()
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private func imageFromResult(_ resultImage: KFCrossPlatformImage) -> Image {
|
|
|
- if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
|
|
|
- return Image(crossPlatformImage: resultImage)
|
|
|
- } else {
|
|
|
- #if canImport(UIKit)
|
|
|
- // The CG image is used to solve #1395
|
|
|
- // It should be not necessary if SwiftUI.Image can handle resizing correctly when created
|
|
|
- // by `Image.init(uiImage:)`. (The orientation information should be already contained in
|
|
|
- // a `UIImage`)
|
|
|
- // https://github.com/onevcat/Kingfisher/issues/1395
|
|
|
- //
|
|
|
- // This issue happens on iOS 13 and was fixed by Apple from iOS 14.
|
|
|
- if let cgImage = resultImage.cgImage {
|
|
|
- return Image(decorative: cgImage, scale: resultImage.scale, orientation: resultImage.imageOrientation.toSwiftUI())
|
|
|
- } else {
|
|
|
- return Image(crossPlatformImage: resultImage)
|
|
|
- }
|
|
|
- #else
|
|
|
- return Image(crossPlatformImage: resultImage)
|
|
|
- #endif
|
|
|
-
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#if canImport(UIKit)
|
|
|
-@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
|
|
-extension UIImage.Orientation {
|
|
|
- func toSwiftUI() -> Image.Orientation {
|
|
|
- switch self {
|
|
|
- case .down: return .down
|
|
|
- case .up: return .up
|
|
|
- case .left: return .left
|
|
|
- case .right: return .right
|
|
|
- case .upMirrored: return .upMirrored
|
|
|
- case .downMirrored: return .downMirrored
|
|
|
- case .leftMirrored: return .leftMirrored
|
|
|
- case .rightMirrored: return .rightMirrored
|
|
|
- @unknown default: return .up
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-#endif
|
|
|
-
|
|
|
// MARK: - Image compatibility.
|
|
|
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
|
|
extension KFImage {
|
|
|
@@ -273,6 +108,41 @@ extension KFImage {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// MARK: - Deprecated
|
|
|
+@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
|
|
+extension KFImage {
|
|
|
+ /// Creates a Kingfisher compatible image view to load image from the given `Source`.
|
|
|
+ /// - Parameter source: The image `Source` defining where to load the target image.
|
|
|
+ /// - Parameter options: The options should be applied when loading the image.
|
|
|
+ /// Some UIKit related options (such as `ImageTransition.flip`) are not supported.
|
|
|
+ /// - Parameter isLoaded: Whether the image is loaded or not. This provides a way to inspect the internal loading
|
|
|
+ /// state. `true` if the image is loaded successfully. Otherwise, `false`. Do not set the
|
|
|
+ /// wrapped value from outside.
|
|
|
+ /// - Deprecated: Some options are not available in SwiftUI yet. Use `KFImage(source:isLoaded:)` to create a
|
|
|
+ /// `KFImage` and configure the options through modifier instead. See methods of `KFOptionSetter`
|
|
|
+ /// for more.
|
|
|
+ @available(*, deprecated, message: "Some options are not available in SwiftUI yet. Use `KFImage(source:isLoaded:)` to create a `KFImage` and configure the options through modifier instead.")
|
|
|
+ public init(source: Source?, options: KingfisherOptionsInfo? = nil, isLoaded: Binding<Bool> = .constant(false)) {
|
|
|
+ let binder = KFImage.ImageBinder(source: source, options: options, isLoaded: isLoaded)
|
|
|
+ self.init(binder: binder)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Creates a Kingfisher compatible image view to load image from the given `URL`.
|
|
|
+ /// - Parameter url: The image URL from where to load the target image.
|
|
|
+ /// - Parameter options: The options should be applied when loading the image.
|
|
|
+ /// Some UIKit related options (such as `ImageTransition.flip`) are not supported.
|
|
|
+ /// - Parameter isLoaded: Whether the image is loaded or not. This provides a way to inspect the internal loading
|
|
|
+ /// state. `true` if the image is loaded successfully. Otherwise, `false`. Do not set the
|
|
|
+ /// wrapped value from outside.
|
|
|
+ /// - Deprecated: Some options are not available in SwiftUI yet. Use `KFImage(_:isLoaded:)` to create a
|
|
|
+ /// `KFImage` and configure the options through modifier instead. See methods of `KFOptionSetter`
|
|
|
+ /// for more.
|
|
|
+ @available(*, deprecated, message: "Some options are not available in SwiftUI yet. Use `KFImage(_:isLoaded:)` to create a `KFImage` and configure the options through modifier instead.")
|
|
|
+ init(_ url: URL?, options: KingfisherOptionsInfo? = nil, isLoaded: Binding<Bool> = .constant(false)) {
|
|
|
+ self.init(source: url?.convertToSource(), options: options, isLoaded: isLoaded)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
#if DEBUG
|
|
|
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
|
|
struct KFImage_Previews : PreviewProvider {
|