Make the ImageManager obseable

This commit is contained in:
DreamPiggy 2020-03-24 17:14:20 +08:00
parent 3d43d8ba29
commit 88e4deab48
2 changed files with 61 additions and 14 deletions

View File

@ -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 {}

View File

@ -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()
}
}