-
Notifications
You must be signed in to change notification settings - Fork 241
/
Copy pathImageManager.swift
164 lines (153 loc) · 6.53 KB
/
ImageManager.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*
* This file is part of the SDWebImage package.
* (c) DreamPiggy <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import SwiftUI
import Combine
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 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
public final class ImageManager : ObservableObject {
/// loaded image, note when progressive loading, this will published multiple times with different partial image
public var image: PlatformImage? {
didSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
/// loaded image data, may be nil if hit from memory cache. This will only published once even on incremental image loading
public var imageData: Data? {
didSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
/// loaded image cache type, .none means from network
public var cacheType: SDImageCacheType = .none {
didSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
/// loading error, you can grab the error code and reason listed in `SDWebImageErrorDomain`, to provide a user interface about the error reason
public var error: Error? {
didSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
/// true means during incremental loading
public var isIncremental: Bool = false {
didSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
/// A observed object to pass through the image manager loading status to indicator
public var indicatorStatus = IndicatorStatus()
weak var currentOperation: SDWebImageOperation? = nil
var currentURL: URL?
var transaction = Transaction()
var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)?
var failureBlock: ((Error) -> Void)?
var progressBlock: ((Int, Int) -> Void)?
public init() {}
/// Start to load the url operation
/// - 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 func load(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil) {
let manager: SDWebImageManager
if let customManager = context?[.customManager] as? SDWebImageManager {
manager = customManager
} else {
manager = .shared
}
if (currentOperation != nil && currentURL == url) {
return
}
currentURL = url
self.indicatorStatus.isLoading = true
self.indicatorStatus.progress = 0
currentOperation = manager.loadImage(with: url, options: options, context: context, progress: { [weak self] (receivedSize, expectedSize, _) in
guard let self = self else {
return
}
let progress: Double
if (expectedSize > 0) {
progress = Double(receivedSize) / Double(expectedSize)
} else {
progress = 0
}
self.indicatorStatus.progress = progress
self.progressBlock?(receivedSize, expectedSize)
}) { [weak self] (image, data, error, cacheType, finished, _) in
guard let self = self else {
return
}
if let error = error as? SDWebImageError, error.code == .cancelled {
// Ignore user cancelled
// There are race condition when quick scroll
// Indicator modifier disapper and trigger `WebImage.body`
// So previous View struct call `onDisappear` and cancel the currentOperation
return
}
withTransaction(transaction) {
self.image = image
self.error = error
self.isIncremental = !finished
if finished {
self.imageData = data
self.cacheType = cacheType
self.indicatorStatus.isLoading = false
self.indicatorStatus.progress = 1
if let image = image {
self.successBlock?(image, data, cacheType)
} else {
self.failureBlock?(error ?? NSError())
}
}
}
}
}
/// Cancel the current url loading
public func cancel() {
if let operation = currentOperation {
operation.cancel()
currentOperation = nil
}
indicatorStatus.isLoading = false
currentURL = nil
}
}
// Completion Handler
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
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.
public func setOnFailure(perform action: ((Error) -> Void)? = nil) {
self.failureBlock = action
}
/// 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 loaded image data, the third arg is the cache type loaded from. If `action` is `nil`, the call has no effect.
public func setOnSuccess(perform action: ((PlatformImage, Data?, SDImageCacheType) -> Void)? = nil) {
self.successBlock = action
}
/// 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.
public func setOnProgress(perform action: ((Int, Int) -> Void)? = nil) {
self.progressBlock = action
}
}