Sfoglia il codice sorgente

Use observed object to show image

onevcat 5 anni fa
parent
commit
cb065e3be5

+ 2 - 1
Demo/Demo/Kingfisher-Demo/SwiftUIViews/ListDemo.swift

@@ -34,7 +34,8 @@ struct ListDemo : View {
 
     var body: some View {
         List(index) { i in
-            ImageCell(index: i).frame(width: 300, height: 300)
+            ImageCell(index: i)
+                .frame(height: 300)
         }.navigationBarTitle(Text("SwiftUI List"), displayMode: .inline)
     }
 }

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

@@ -35,10 +35,11 @@ struct SizingAnimationDemo: View {
     var body: some View {
         VStack {
             KFImage(URL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/Loading/kingfisher-1.jpg")!)
+                .loadImmediately()
                 .resizable()
                 .aspectRatio(contentMode: .fill)
                 .frame(width: imageSize, height: imageSize)
-                .clipShape(RoundedRectangle(cornerRadius: isPlaying ? 0 : 50, style: .continuous))
+                .clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous))
                 .frame(width: 350, height: 350)
             Button(action: {
                 playButtonAction()

+ 33 - 13
Sources/SwiftUI/ImageBinder.swift

@@ -33,7 +33,7 @@ extension KFImage {
 
     /// Represents a binder for `KFImage`. It takes responsibility as an `ObjectBinding` and performs
     /// image downloading and progress reporting based on `KingfisherManager`.
-    class ImageBinder {
+    class ImageBinder: ObservableObject {
 
         let source: Source?
         var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions)
@@ -50,7 +50,8 @@ extension KFImage {
 
         var isLoaded: Binding<Bool>
 
-        weak var loadedImage: KFCrossPlatformImage? = nil
+        @Published var loaded = false
+        @Published var loadedImage: KFCrossPlatformImage? = nil
 
         @available(*, deprecated, message: "The `options` version is deprecated And will be removed soon.")
         init(source: Source?, options: KingfisherOptionsInfo? = nil, isLoaded: Binding<Bool>) {
@@ -76,7 +77,7 @@ extension KFImage {
             self.isLoaded = isLoaded
         }
 
-        func start(_ done: @escaping (Result<RetrieveImageResult, KingfisherError>) -> Void) {
+        func start() {
 
             guard !loadingOrSucceeded else { return }
 
@@ -102,21 +103,17 @@ extension KFImage {
                         switch result {
                         case .success(let value):
                             self.loadedImage = value.image
-                            let r = RetrieveImageResult(
-                                image: value.image, cacheType: value.cacheType, source: value.source, originalSource: value.originalSource
-                            )
-                            CallbackQueue.mainCurrentOrAsync.execute {
-                                done(.success(r))
-                            }
+
+                            self.isLoaded.wrappedValue = true
+
+                            let animation = self.fadeTransitionDuration(cacheType: value.cacheType)
+                                .map { duration in Animation.linear(duration: duration) }
+                            withAnimation(animation) { self.loaded = true }
 
                             CallbackQueue.mainAsync.execute {
-                                self.isLoaded.wrappedValue = true
                                 self.onSuccessDelegate.call(value)
                             }
                         case .failure(let error):
-                            CallbackQueue.mainCurrentOrAsync.execute {
-                                done(.failure(error))
-                            }
                             CallbackQueue.mainAsync.execute {
                                 self.onFailureDelegate.call(error)
                             }
@@ -127,6 +124,17 @@ extension KFImage {
         /// Cancels the download task if it is in progress.
         func cancel() {
             downloadTask?.cancel()
+            downloadTask = nil
+        }
+
+        private func shouldApplyFade(cacheType: CacheType) -> Bool {
+            options.forceTransition || cacheType == .none
+        }
+
+        private func fadeTransitionDuration(cacheType: CacheType) -> TimeInterval? {
+            shouldApplyFade(cacheType: cacheType)
+                ? options.transition.fadeDuration
+                : nil
         }
     }
 }
@@ -142,4 +150,16 @@ extension KFImage.ImageBinder: Hashable {
         hasher.combine(options.processor.identifier)
     }
 }
+
+extension ImageTransition {
+    // Only for fade effect in SwiftUI.
+    fileprivate var fadeDuration: TimeInterval? {
+        switch self {
+        case .fade(let duration):
+            return duration
+        default:
+            return nil
+        }
+    }
+}
 #endif

+ 10 - 45
Sources/SwiftUI/KFImage.swift

@@ -105,6 +105,11 @@ public struct KFImage: View {
         KFImageRenderer(context)
             .id(context.binder)
     }
+
+    public func loadImmediately() -> KFImage {
+        context.binder.start()
+        return self
+    }
 }
 
 @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@@ -127,10 +132,7 @@ extension KFImage {
 struct KFImageRenderer: View {
 
     /// An image binder that manages loading and cancelling image related task.
-    private let binder: KFImage.ImageBinder
-
-    @State private var loadingResult: Result<RetrieveImageResult, KingfisherError>?
-    @State private var isLoaded = false
+    @ObservedObject var binder: KFImage.ImageBinder
 
     // Acts as a placeholder when loading an image.
     var placeholder: AnyView?
@@ -150,17 +152,12 @@ struct KFImageRenderer: View {
 
     /// Declares the content and behavior of this view.
     var body: some View {
-        if case .success(let r) = loadingResult {
-            configurations
-                .reduce(imageFromResult(r.image)) {
-                    current, config in config(current)
-                }
-                .opacity(isLoaded ? 1.0 : 0.0)
-        } else if let image = binder.loadedImage {
+        if let image = binder.loadedImage {
             configurations
-                .reduce(Image(crossPlatformImage: image)) {
+                .reduce(imageFromResult(image)) {
                     current, config in config(current)
                 }
+                .opacity(binder.loaded ? 1.0 : 0.0)
         } else {
             Group {
                 if placeholder != nil {
@@ -174,17 +171,7 @@ struct KFImageRenderer: View {
                     return
                 }
                 if !binder.loadingOrSucceeded {
-                    binder.start { result in
-                        self.loadingResult = result
-                        switch result {
-                        case .success(let value):
-                            let animation = fadeTransitionDuration(cacheType: value.cacheType)
-                                .map { duration in Animation.linear(duration: duration) }
-                            withAnimation(animation) { isLoaded = true }
-                        case .failure(_):
-                            break
-                        }
-                    }
+                    binder.start()
                 }
             }
             .onDisappear { [weak binder = self.binder] in
@@ -221,28 +208,6 @@ struct KFImageRenderer: View {
 
         }
     }
-
-    private func shouldApplyFade(cacheType: CacheType) -> Bool {
-        binder.options.forceTransition || cacheType == .none
-    }
-
-    private func fadeTransitionDuration(cacheType: CacheType) -> TimeInterval? {
-        shouldApplyFade(cacheType: cacheType)
-            ? binder.options.transition.fadeDuration
-            : nil
-    }
-}
-
-extension ImageTransition {
-    // Only for fade effect in SwiftUI.
-    fileprivate var fadeDuration: TimeInterval? {
-        switch self {
-        case .fade(let duration):
-            return duration
-        default:
-            return nil
-        }
-    }
 }
 
 #if canImport(UIKit)