diff --git a/SDWebImageSwiftUI/Classes/ImageManager.swift b/SDWebImageSwiftUI/Classes/ImageManager.swift index e54f145..eccb21d 100644 --- a/SDWebImageSwiftUI/Classes/ImageManager.swift +++ b/SDWebImageSwiftUI/Classes/ImageManager.swift @@ -9,16 +9,21 @@ import SwiftUI import SDWebImage +/// A Image observable object for handle image load process. This drive the Source of Truth for image loading status. +/// You can use `@ObservedObject` to associate each instance of manager to your View type, which update your view's body from SwiftUI framework when image was loaded. @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) -class ImageManager : ObservableObject, IndicatorReportable { - @Published var image: PlatformImage? // loaded image, note when progressive loading, this will published multiple times with different partial image - @Published var isLoading: Bool = false // whether network is loading or cache is querying, should only be used for indicator binding - @Published var progress: Double = 0 // network progress, should only be used for indicator binding +public final class ImageManager : ObservableObject { + /// loaded image, note when progressive loading, this will published multiple times with different partial image + @Published public var image: PlatformImage? + /// whether network is loading or cache is querying, should only be used for indicator binding + @Published public var isLoading: Bool = false + /// network progress, should only be used for indicator binding + @Published public var progress: Double = 0 + /// true means during incremental loading + @Published public var isIncremental: Bool = false var manager: SDWebImageManager weak var currentOperation: SDWebImageOperation? = nil - var isSuccess: Bool = false // true means request for this URL is ended forever, load() do nothing - var isIncremental: Bool = false // true means during incremental loading var isFirstLoad: Bool = true // false after first call `load()` var url: URL? @@ -28,7 +33,11 @@ class ImageManager : ObservableObject, IndicatorReportable { var failureBlock: ((Error) -> Void)? var progressBlock: ((Int, Int) -> Void)? - init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil) { + /// Create a image manager for loading the specify url, with custom options and context. + /// - Parameter url: The image url + /// - Parameter options: The options to use when downloading the image. See `SDWebImageOptions` for the possible values. + /// - Parameter context: A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. + public init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil) { self.url = url self.options = options self.context = context @@ -39,7 +48,8 @@ class ImageManager : ObservableObject, IndicatorReportable { } } - func load() { + /// Start to load the url operation + public func load() { isFirstLoad = false if currentOperation != nil { return @@ -76,7 +86,6 @@ class ImageManager : ObservableObject, IndicatorReportable { self.isLoading = false self.progress = 1 if let image = image { - self.isSuccess = true self.successBlock?(image, cacheType) } else { self.failureBlock?(error ?? NSError()) @@ -85,9 +94,46 @@ class ImageManager : ObservableObject, IndicatorReportable { } } - func cancel() { - currentOperation?.cancel() - currentOperation = nil + /// Cancel the current url loading + public func cancel() { + if let operation = currentOperation { + operation.cancel() + currentOperation = nil + isLoading = false + } } } + +// Completion Handler +extension ImageManager { + /// Provide the action when image load fails. + /// - Parameters: + /// - action: The action to perform. The first arg is the error during loading. If `action` is `nil`, the call has no effect. + /// - Returns: A view that triggers `action` when this image load fails. + public func onFailure(perform action: ((Error) -> Void)? = nil) -> ImageManager { + self.failureBlock = action + return self + } + + /// Provide the action when image load successes. + /// - Parameters: + /// - action: The action to perform. The first arg is the loaded image, the second arg is the cache type loaded from. If `action` is `nil`, the call has no effect. + /// - Returns: A view that triggers `action` when this image load successes. + public func onSuccess(perform action: ((PlatformImage, SDImageCacheType) -> Void)? = nil) -> ImageManager { + self.successBlock = action + return self + } + + /// Provide the action when image load progress changes. + /// - Parameters: + /// - action: The action to perform. The first arg is the received size, the second arg is the total size, all in bytes. If `action` is `nil`, the call has no effect. + /// - Returns: A view that triggers `action` when this image load successes. + public func onProgress(perform action: ((Int, Int) -> Void)? = nil) -> ImageManager { + self.progressBlock = action + return self + } +} + +// Indicator Reportor +extension ImageManager: IndicatorReportable {} diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index 96d4a14..5262f84 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -120,14 +120,15 @@ public struct WebImage : View { .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) .onAppear { guard self.retryOnAppear else { return } - if !self.imageManager.isSuccess { + // When using prorgessive loading, the new partial image will cause onAppear. Filter this case + if self.imageManager.image == nil && !self.imageManager.isIncremental { self.imageManager.load() } } .onDisappear { guard self.cancelOnDisappear else { return } // When using prorgessive loading, the previous partial image will cause onDisappear. Filter this case - if !self.imageManager.isSuccess && !self.imageManager.isIncremental { + if self.imageManager.image == nil && !self.imageManager.isIncremental { self.imageManager.cancel() } }