Browse Source

Introduce the `onFailureView` modifier

This modifier provides more flexibility than `onFailureImage` that allows any SwiftUI `View` to be used in case image loading fails.

Fixes: #2082
Abdulaziz Alobaili 7 months ago
parent
commit
1321e20d57

+ 7 - 0
Sources/SwiftUI/ImageBinder.swift

@@ -52,6 +52,7 @@ extension KFImage {
         private(set) var animating = false
 
         var loadedImage: KFCrossPlatformImage? = nil { willSet { objectWillChange.send() } }
+        var failureView: (() -> AnyView)? = nil { willSet { objectWillChange.send() } }
         var progress: Progress = .init()
 
         func markLoading() {
@@ -72,6 +73,9 @@ extension KFImage {
                     if let image = context.options.onFailureImage {
                         self.loadedImage = image
                     }
+                    if let view = context.failureView {
+                        self.failureView = view
+                    }
                     self.loading = false
                     self.markLoaded(sendChangeEvent: false)
                 }
@@ -129,6 +133,9 @@ extension KFImage {
                                 if let image = context.options.onFailureImage {
                                     self.loadedImage = image
                                 }
+                                if let view = context.failureView {
+                                    self.failureView = view
+                                }
                                 self.markLoaded(sendChangeEvent: false)
                             }
                             

+ 7 - 1
Sources/SwiftUI/ImageContext.swift

@@ -78,7 +78,13 @@ extension KFImage {
             get { propertyQueue.sync { _placeholder } }
             set { propertyQueue.sync { _placeholder = newValue } }
         }
-        
+
+        var _failureView: (() -> AnyView)? = nil
+        var failureView: (() -> AnyView)? {
+            get { propertyQueue.sync { _failureView } }
+            set { propertyQueue.sync { _failureView = newValue } }
+        }
+
         var _startLoadingBeforeViewAppear: Bool = false
         var startLoadingBeforeViewAppear: Bool {
             get { propertyQueue.sync { _startLoadingBeforeViewAppear } }

+ 9 - 0
Sources/SwiftUI/KFImageOptions.swift

@@ -123,6 +123,15 @@ extension KFImageProtocol {
         placeholder { _ in content() }
     }
 
+    /// Sets a failure `View` that is displayed when the image fails to load.
+    ///
+    /// - Parameter content: A view that represents failure.
+    /// - Returns: A Kingfisher-compatible image view that includes the provided `content` as its failure.
+    public func onFailureView<F: View>(@ViewBuilder _ content: @escaping () -> F) -> Self {
+        context.failureView = { AnyView(content()) }
+        return self
+    }
+
     /// Enables canceling the download task associated with `self` when the view disappears.
     ///
     /// - Parameter flag: A boolean value indicating whether to cancel the task.

+ 7 - 3
Sources/SwiftUI/KFImageRenderer.swift

@@ -46,10 +46,14 @@ struct KFImageRenderer<HoldingView> : View where HoldingView: KFImageHoldingView
             renderedImage().opacity(binder.loaded ? 1.0 : 0.0)
             if binder.loadedImage == nil {
                 ZStack {
-                    if let placeholder = context.placeholder {
-                        placeholder(binder.progress)
+                    if let failureView = binder.failureView {
+                        failureView()
                     } else {
-                        Color.clear
+                        if let placeholder = context.placeholder {
+                            placeholder(binder.progress)
+                        } else {
+                            Color.clear
+                        }
                     }
                 }
                 .onAppear { [weak binder = self.binder] in