Răsfoiți Sursa

Merge pull request #1707 from onevcat/feature/StateObject

`@StateObject`  based image binder
Wei Wang 4 ani în urmă
părinte
comite
e724dde3fa

+ 2 - 2
Demo/Demo/Kingfisher-Demo/SwiftUIViews/AnimatedImageDemo.swift

@@ -27,7 +27,7 @@
 import SwiftUI
 import Kingfisher
 
-@available(iOS 13.0, *)
+@available(iOS 14.0, *)
 struct AnimatedImageDemo: View {
     
     @State private var index = 1
@@ -66,7 +66,7 @@ struct AnimatedImageDemo: View {
     
 }
 
-@available(iOS 13.0, *)
+@available(iOS 14.0, *)
 struct AnimatedImageDemo_Previews: PreviewProvider {
     
     static var previews: some View {

+ 51 - 0
Demo/Demo/Kingfisher-Demo/SwiftUIViews/GeometryReaderDemo.swift

@@ -0,0 +1,51 @@
+//
+//  GeometryReaderDemo.swift
+//  Kingfisher
+//
+//  Created by JP20028 on 2021/06/12.
+//
+//  Copyright (c) 2021 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.
+
+import SwiftUI
+import Kingfisher
+
+@available(iOS 14.0, *)
+struct GeometryReaderDemo: View {
+    var body: some View {
+        GeometryReader { geo in
+            KFImage(
+                ImageLoader.sampleImageURLs.first
+            )
+                .placeholder { ProgressView() }
+                .forceRefresh()
+                .resizable()
+                .scaledToFit()
+                .frame(width: geo.size.width)
+        }
+    }
+}
+
+@available(iOS 14.0, *)
+struct GeometryReaderDemo_Previews: PreviewProvider {
+    static var previews: some View {
+        GeometryReaderDemo()
+    }
+}

+ 3 - 3
Demo/Demo/Kingfisher-Demo/SwiftUIViews/ListDemo.swift

@@ -27,7 +27,7 @@
 import Kingfisher
 import SwiftUI
 
-@available(iOS 13.0, *)
+@available(iOS 14.0, *)
 struct ListDemo : View {
 
     let index = 1 ..< 700
@@ -40,7 +40,7 @@ struct ListDemo : View {
     }
 }
 
-@available(iOS 13.0, *)
+@available(iOS 14.0, *)
 struct ImageCell: View {
 
     @State var done = false
@@ -89,7 +89,7 @@ struct ImageCell: View {
 
 }
 
-@available(iOS 13.0, *)
+@available(iOS 14.0, *)
 struct SwiftUIList_Previews : PreviewProvider {
     static var previews: some View {
         ListDemo()

+ 5 - 6
Demo/Demo/Kingfisher-Demo/SwiftUIViews/MainView.swift

@@ -27,7 +27,7 @@
 import SwiftUI
 import Kingfisher
 
-@available(iOS 13.0, *)
+@available(iOS 14.0, *)
 struct MainView: View {
     var body: some View {
         List {
@@ -43,16 +43,15 @@ struct MainView: View {
             NavigationLink(destination: SingleViewDemo()) { Text("Basic Image") }
             NavigationLink(destination: SizingAnimationDemo()) { Text("Sizing Toggle") }
             NavigationLink(destination: ListDemo()) { Text("List") }
-            if #available(iOS 14.0, *) {
-                NavigationLink(destination: LazyVStackDemo()) { Text("Stack") }
-                NavigationLink(destination: GridDemo()) { Text("Grid") }
-            }
+            NavigationLink(destination: LazyVStackDemo()) { Text("Stack") }
+            NavigationLink(destination: GridDemo()) { Text("Grid") }
             NavigationLink(destination: AnimatedImageDemo()) { Text("Animated Image") }
+            NavigationLink(destination: GeometryReaderDemo()) { Text("Geometry Reader") }
         }.navigationBarTitle(Text("SwiftUI Sample"))
     }
 }
 
-@available(iOS 13.0, *)
+@available(iOS 14.0, *)
 struct MainView_Previews: PreviewProvider {
     static var previews: some View {
         MainView()

+ 2 - 2
Demo/Demo/Kingfisher-Demo/SwiftUIViews/SingleViewDemo.swift

@@ -27,7 +27,7 @@
 import Kingfisher
 import SwiftUI
 
-@available(iOS 13.0, *)
+@available(iOS 14.0, *)
 struct SingleViewDemo : View {
 
     @State private var index = 1
@@ -72,7 +72,7 @@ struct SingleViewDemo : View {
     }
 }
 
-@available(iOS 13.0, *)
+@available(iOS 14.0, *)
 struct SingleViewDemo_Previews : PreviewProvider {
     static var previews: some View {
         SingleViewDemo()

+ 2 - 2
Demo/Demo/Kingfisher-Demo/SwiftUIViews/SizingAnimationDemo.swift

@@ -27,7 +27,7 @@
 import SwiftUI
 import Kingfisher
 
-@available(iOS 13.0, *)
+@available(iOS 14.0, *)
 struct SizingAnimationDemo: View {
     @State var imageSize: CGFloat = 250
     @State var isPlaying = false
@@ -62,7 +62,7 @@ struct SizingAnimationDemo: View {
     }
 }
 
-@available(iOS 13.0, *)
+@available(iOS 14.0, *)
 struct SizingAnimationDemo_Previews: PreviewProvider {
     static var previews: some View {
         SizingAnimationDemo()

+ 1 - 1
Demo/Demo/Kingfisher-Demo/ViewControllers/SwiftUIViewController.swift

@@ -27,7 +27,7 @@
 import SwiftUI
 import UIKit
 
-@available(iOS 13.0, *)
+@available(iOS 14.0, *)
 class SwiftUIViewController: UIHostingController<MainView> {
     required init?(coder: NSCoder) {
         super.init(coder: coder, rootView: MainView())

+ 4 - 0
Demo/Kingfisher-Demo.xcodeproj/project.pbxproj

@@ -17,6 +17,7 @@
 		4B4307A51D87E6A700ED2DA9 /* loader.gif in Resources */ = {isa = PBXBuildFile; fileRef = 4B7742461D87E42E0077024E /* loader.gif */; };
 		4B7742471D87E42E0077024E /* loader.gif in Resources */ = {isa = PBXBuildFile; fileRef = 4B7742461D87E42E0077024E /* loader.gif */; };
 		4B7742481D87E42E0077024E /* loader.gif in Resources */ = {isa = PBXBuildFile; fileRef = 4B7742461D87E42E0077024E /* loader.gif */; };
+		4B779C8526743C2800FF9C1E /* GeometryReaderDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B779C8426743C2800FF9C1E /* GeometryReaderDemo.swift */; };
 		4B92FE5625FF906B00473088 /* AutoSizingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92FE5525FF906B00473088 /* AutoSizingTableViewController.swift */; };
 		4BCCF33D1D5B02F8003387C2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCCF3361D5B02F8003387C2 /* AppDelegate.swift */; };
 		4BCCF33E1D5B02F8003387C2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4BCCF3371D5B02F8003387C2 /* Assets.xcassets */; };
@@ -158,6 +159,7 @@
 		4B1C7A3C21A256E300CE9D31 /* InfinityCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfinityCollectionViewController.swift; sourceTree = "<group>"; };
 		4B2944551C3D03880088C3E7 /* Kingfisher-macOS-Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Kingfisher-macOS-Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
 		4B7742461D87E42E0077024E /* loader.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = loader.gif; sourceTree = "<group>"; };
+		4B779C8426743C2800FF9C1E /* GeometryReaderDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeometryReaderDemo.swift; sourceTree = "<group>"; };
 		4B92FE5525FF906B00473088 /* AutoSizingTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoSizingTableViewController.swift; sourceTree = "<group>"; };
 		4BCCF3361D5B02F8003387C2 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		4BCCF3371D5B02F8003387C2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -413,6 +415,7 @@
 				D1F78A622589F17200930759 /* MainView.swift */,
 				D1F78A612589F17200930759 /* ListDemo.swift */,
 				D198F42125EDC4B900C53E0D /* GridDemo.swift */,
+				4B779C8426743C2800FF9C1E /* GeometryReaderDemo.swift */,
 				D1F78A632589F17200930759 /* SingleViewDemo.swift */,
 				D198F41D25EDC11500C53E0D /* LazyVStackDemo.swift */,
 				D198F41F25EDC34000C53E0D /* SizingAnimationDemo.swift */,
@@ -680,6 +683,7 @@
 				D1E4CF5421BACBA6004D029D /* ImageDataProviderCollectionViewController.swift in Sources */,
 				D1FAB06F21A853E600908910 /* HighResolutionCollectionViewController.swift in Sources */,
 				D1F78A652589F17200930759 /* MainView.swift in Sources */,
+				4B779C8526743C2800FF9C1E /* GeometryReaderDemo.swift in Sources */,
 				D1F78A5F2589F0AA00930759 /* SwiftUIViewController.swift in Sources */,
 				D12E0C951C47F91800AC98AD /* AppDelegate.swift in Sources */,
 				D1F06F3321AA4292000B1C38 /* DetailImageViewController.swift in Sources */,

+ 3 - 2
Sources/SwiftUI/ImageBinder.swift

@@ -26,8 +26,9 @@
 
 #if canImport(SwiftUI) && canImport(Combine)
 import SwiftUI
+import Combine
 
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 extension KFImage {
 
     /// Represents a binder for `KFImage`. It takes responsibility as an `ObjectBinding` and performs
@@ -139,7 +140,7 @@ extension KFImage {
     }
 }
 
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 extension KFImage.ImageBinder: Hashable {
     static func == (lhs: KFImage.ImageBinder, rhs: KFImage.ImageBinder) -> Bool {
         lhs.source == rhs.source && lhs.options.processor.identifier == rhs.options.processor.identifier

+ 3 - 2
Sources/SwiftUI/ImageContext.swift

@@ -26,8 +26,9 @@
 
 #if canImport(SwiftUI) && canImport(Combine)
 import SwiftUI
+import Combine
 
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 extension KFImage {
     public struct Context<HoldingView: KFImageHoldingView> {
         var binder: ImageBinder
@@ -42,7 +43,7 @@ extension KFImage {
 }
 
 #if canImport(UIKit) && !os(watchOS)
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 extension KFAnimatedImage {
     public typealias Context = KFImage.Context
     typealias ImageBinder = KFImage.ImageBinder

+ 4 - 3
Sources/SwiftUI/KFAnimatedImage.swift

@@ -26,8 +26,9 @@
 
 #if canImport(SwiftUI) && canImport(Combine) && canImport(UIKit) && !os(watchOS)
 import SwiftUI
+import Combine
 
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 public struct KFAnimatedImage: KFImageProtocol {
     public typealias HoldingView = KFAnimatedImageViewRepresenter
     public var context: Context<HoldingView>
@@ -37,7 +38,7 @@ public struct KFAnimatedImage: KFImageProtocol {
 }
 
 /// A wrapped `UIViewRepresentable` of `AnimatedImageView`
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 public struct KFAnimatedImageViewRepresenter: UIViewRepresentable, KFImageHoldingView {
     public static func created(from image: KFCrossPlatformImage) -> KFAnimatedImageViewRepresenter {
         KFAnimatedImageViewRepresenter(image: image)
@@ -62,7 +63,7 @@ public struct KFAnimatedImageViewRepresenter: UIViewRepresentable, KFImageHoldin
 }
 
 #if DEBUG
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 struct KFAnimatedImage_Previews : PreviewProvider {
     static var previews: some View {
         Group {

+ 7 - 26
Sources/SwiftUI/KFImage.swift

@@ -26,8 +26,9 @@
 
 #if canImport(SwiftUI) && canImport(Combine)
 import SwiftUI
+import Combine
 
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 public struct KFImage: KFImageProtocol {
     public var context: Context<Image>
     public init(context: Context<Image>) {
@@ -35,35 +36,15 @@ public struct KFImage: KFImageProtocol {
     }
 }
 
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 extension Image: KFImageHoldingView {
     public static func created(from image: KFCrossPlatformImage) -> Image {
-        if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
-            return Image(crossPlatformImage: image)
-        } 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 = image.cgImage {
-                return Image(decorative: cgImage, scale: image.scale, orientation: image.imageOrientation.toSwiftUI())
-            } else {
-                return Image(crossPlatformImage: image)
-            }
-            #else
-            return Image(crossPlatformImage: image)
-            #endif
-
-        }
+        Image(crossPlatformImage: image)
     }
 }
 
 // MARK: - Image compatibility.
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 extension KFImage {
 
     public func resizable(
@@ -87,7 +68,7 @@ extension KFImage {
 }
 
 // MARK: - Deprecated
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.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.
@@ -122,7 +103,7 @@ extension KFImage {
 }
 
 #if DEBUG
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 struct KFImage_Previews : PreviewProvider {
     static var previews: some View {
         Group {

+ 3 - 2
Sources/SwiftUI/KFImageOptions.swift

@@ -26,9 +26,10 @@
 
 #if canImport(SwiftUI) && canImport(Combine)
 import SwiftUI
+import Combine
 
 // MARK: - KFImage creating.
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 extension KFImageProtocol {
 
     /// Creates a `KFImage` for a given `Source`.
@@ -109,7 +110,7 @@ extension KFImageProtocol {
     }
 }
 
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 extension KFImageProtocol {
     /// Sets a placeholder `View` which shows when loading the image.
     /// - Parameter content: A view that describes the placeholder.

+ 11 - 5
Sources/SwiftUI/KFImageProtocol.swift

@@ -26,18 +26,24 @@
 
 #if canImport(SwiftUI) && canImport(Combine)
 import SwiftUI
+import Combine
 
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 public protocol KFImageProtocol: View, KFOptionSetter {
     associatedtype HoldingView: KFImageHoldingView
     var context: KFImage.Context<HoldingView> { get set }
     init(context: KFImage.Context<HoldingView>)
 }
 
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 extension KFImageProtocol {
     public var body: some View {
-        KFImageRenderer<HoldingView>(context)
+        KFImageRenderer<HoldingView>(
+            binder: context.binder,
+            placeholder: context.placeholder,
+            cancelOnDisappear: context.cancelOnDisappear,
+            configurations: context.configurations
+        )
             .id(context.binder)
     }
     
@@ -91,12 +97,12 @@ extension KFImageProtocol {
     }
 }
 
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 public protocol KFImageHoldingView: View {
     static func created(from image: KFCrossPlatformImage) -> Self
 }
 
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 extension KFImageProtocol {
     public var options: KingfisherParsedOptionsInfo {
         get { context.binder.options }

+ 5 - 11
Sources/SwiftUI/KFImageRenderer.swift

@@ -26,14 +26,15 @@
 
 #if canImport(SwiftUI) && canImport(Combine)
 import SwiftUI
+import Combine
 
 /// 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, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 struct KFImageRenderer<HoldingView> : View where HoldingView: KFImageHoldingView {
     
     /// An image binder that manages loading and cancelling image related task.
-    @ObservedObject var binder: KFImage.ImageBinder
+    @StateObject var binder: KFImage.ImageBinder
 
     // Acts as a placeholder when loading an image.
     var placeholder: AnyView?
@@ -44,13 +45,6 @@ struct KFImageRenderer<HoldingView> : View where HoldingView: KFImageHoldingView
     // Configurations should be performed on the image.
     let configurations: [(HoldingView) -> HoldingView]
 
-    init(_ context: KFImage.Context<HoldingView>) {
-        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 {
@@ -88,7 +82,7 @@ struct KFImageRenderer<HoldingView> : View where HoldingView: KFImageHoldingView
     }
 }
 
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 extension Image {
     // Creates an Image with either UIImage or NSImage.
     init(crossPlatformImage: KFCrossPlatformImage) {
@@ -101,7 +95,7 @@ extension Image {
 }
 
 #if canImport(UIKit)
-@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
+@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
 extension UIImage.Orientation {
     func toSwiftUI() -> Image.Orientation {
         switch self {