Skip to content

Commit 04c1ebb

Browse files
committed
Refactor AnimatedImage implementation
Use context.coordinator to store loading status because it's exclusive unlike normal SwiftUI.View
1 parent 2398f56 commit 04c1ebb

File tree

1 file changed

+59
-36
lines changed

1 file changed

+59
-36
lines changed

SDWebImageSwiftUI/Classes/AnimatedImage.swift

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ public final class AnimatedImageCoordinator: NSObject {
2020

2121
/// Any user-provided info stored into coordinator, such as status value used for coordinator
2222
public var userInfo: [AnyHashable : Any]?
23+
24+
var imageLoading = AnimatedLoadingModel()
2325
}
2426

2527
/// Data Binding Object, only properties in this object can support changes from user with @State and refresh
@@ -41,6 +43,8 @@ final class AnimatedImageModel : ObservableObject {
4143
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
4244
final class AnimatedLoadingModel : ObservableObject {
4345
@Published var image: PlatformImage? // loaded image, note when progressive loading, this will published multiple times with different partial image
46+
@Published var isLoading: Bool = false // whether network is loading or cache is querying, should only be used for indicator binding
47+
@Published var progress: Double = 0 // network progress, should only be used for indicator binding
4448

4549
/// Used for loading status recording to avoid recursive `updateView`. There are 3 types of loading (Name/Data/URL)
4650
@Published var imageName: String?
@@ -97,11 +101,18 @@ final class AnimatedImageConfiguration: ObservableObject {
97101
/// A Image View type to load image from url, data or bundle. Supports animated and static image format.
98102
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
99103
public struct AnimatedImage : PlatformViewRepresentable {
100-
@Backport.StateObject var imageModel = AnimatedImageModel()
101-
@Backport.StateObject var imageLoading = AnimatedLoadingModel()
102-
@Backport.StateObject var imageHandler = AnimatedImageHandler()
103-
@Backport.StateObject var imageLayout = AnimatedImageLayout()
104-
@Backport.StateObject var imageConfiguration = AnimatedImageConfiguration()
104+
@SwiftUI.StateObject var imageModel_SwiftUI = AnimatedImageModel()
105+
@Backport.StateObject var imageModel_Backport = AnimatedImageModel()
106+
var imageModel: AnimatedImageModel {
107+
if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) {
108+
return imageModel_SwiftUI
109+
} else {
110+
return imageModel_Backport
111+
}
112+
}
113+
@ObservedObject var imageHandler = AnimatedImageHandler()
114+
@ObservedObject var imageLayout = AnimatedImageLayout()
115+
@ObservedObject var imageConfiguration = AnimatedImageConfiguration()
105116

106117
/// A observed object to pass through the image manager loading status to indicator
107118
@ObservedObject var indicatorStatus = IndicatorStatus()
@@ -128,10 +139,11 @@ public struct AnimatedImage : PlatformViewRepresentable {
128139
/// - 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.
129140
/// - Parameter isAnimating: The binding for animation control
130141
public init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding<Bool>) {
131-
self._isAnimating = isAnimating
132-
self.imageModel.url = url
133-
self.imageModel.webOptions = options
134-
self.imageModel.webContext = context
142+
let imageModel = AnimatedImageModel()
143+
imageModel.url = url
144+
imageModel.webOptions = options
145+
imageModel.webContext = context
146+
self.init(imageModel: imageModel, isAnimating: isAnimating)
135147
}
136148

137149
/// Create an animated image with name and bundle.
@@ -148,9 +160,10 @@ public struct AnimatedImage : PlatformViewRepresentable {
148160
/// - Parameter bundle: The bundle contains image
149161
/// - Parameter isAnimating: The binding for animation control
150162
public init(name: String, bundle: Bundle? = nil, isAnimating: Binding<Bool>) {
151-
self._isAnimating = isAnimating
152-
self.imageModel.name = name
153-
self.imageModel.bundle = bundle
163+
let imageModel = AnimatedImageModel()
164+
imageModel.name = name
165+
imageModel.bundle = bundle
166+
self.init(imageModel: imageModel, isAnimating: isAnimating)
154167
}
155168

156169
/// Create an animated image with data and scale.
@@ -165,9 +178,19 @@ public struct AnimatedImage : PlatformViewRepresentable {
165178
/// - Parameter scale: The scale factor
166179
/// - Parameter isAnimating: The binding for animation control
167180
public init(data: Data, scale: CGFloat = 1, isAnimating: Binding<Bool>) {
181+
let imageModel = AnimatedImageModel()
182+
imageModel.data = data
183+
imageModel.scale = scale
184+
self.init(imageModel: imageModel, isAnimating: isAnimating)
185+
}
186+
187+
init(imageModel: AnimatedImageModel, isAnimating: Binding<Bool>) {
168188
self._isAnimating = isAnimating
169-
self.imageModel.data = data
170-
self.imageModel.scale = scale
189+
if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) {
190+
_imageModel_SwiftUI = SwiftUI.StateObject(wrappedValue: imageModel)
191+
} else {
192+
_imageModel_Backport = Backport.StateObject(wrappedValue: imageModel)
193+
}
171194
}
172195

173196
#if os(macOS)
@@ -183,23 +206,23 @@ public struct AnimatedImage : PlatformViewRepresentable {
183206
}
184207

185208
#if os(macOS)
186-
public func makeNSView(context: NSViewRepresentableContext<AnimatedImage>) -> AnimatedImageViewWrapper {
209+
public func makeNSView(context: Context) -> AnimatedImageViewWrapper {
187210
makeView(context: context)
188211
}
189212

190-
public func updateNSView(_ nsView: AnimatedImageViewWrapper, context: NSViewRepresentableContext<AnimatedImage>) {
213+
public func updateNSView(_ nsView: AnimatedImageViewWrapper, context: Context) {
191214
updateView(nsView, context: context)
192215
}
193216

194217
public static func dismantleNSView(_ nsView: AnimatedImageViewWrapper, coordinator: Coordinator) {
195218
dismantleView(nsView, coordinator: coordinator)
196219
}
197220
#elseif os(iOS) || os(tvOS)
198-
public func makeUIView(context: UIViewRepresentableContext<AnimatedImage>) -> AnimatedImageViewWrapper {
221+
public func makeUIView(context: Context) -> AnimatedImageViewWrapper {
199222
makeView(context: context)
200223
}
201224

202-
public func updateUIView(_ uiView: AnimatedImageViewWrapper, context: UIViewRepresentableContext<AnimatedImage>) {
225+
public func updateUIView(_ uiView: AnimatedImageViewWrapper, context: Context) {
203226
updateView(uiView, context: context)
204227
}
205228

@@ -229,24 +252,24 @@ public struct AnimatedImage : PlatformViewRepresentable {
229252
}
230253

231254
func loadImage(_ view: AnimatedImageViewWrapper, context: Context) {
232-
self.indicatorStatus.isLoading = true
233-
let options = imageModel.webOptions
234-
if options.contains(.delayPlaceholder) {
255+
context.coordinator.imageLoading.isLoading = true
256+
let webOptions = imageModel.webOptions
257+
if webOptions.contains(.delayPlaceholder) {
235258
self.imageConfiguration.placeholderView?.isHidden = true
236259
} else {
237260
self.imageConfiguration.placeholderView?.isHidden = false
238261
}
239-
var context = imageModel.webContext ?? [:]
240-
context[.animatedImageClass] = SDAnimatedImage.self
241-
view.wrapped.sd_internalSetImage(with: imageModel.url, placeholderImage: imageConfiguration.placeholder, options: options, context: context, setImageBlock: nil, progress: { (receivedSize, expectedSize, _) in
262+
var webContext = imageModel.webContext ?? [:]
263+
webContext[.animatedImageClass] = SDAnimatedImage.self
264+
view.wrapped.sd_internalSetImage(with: imageModel.url, placeholderImage: imageConfiguration.placeholder, options: webOptions, context: webContext, setImageBlock: nil, progress: { (receivedSize, expectedSize, _) in
242265
let progress: Double
243266
if (expectedSize > 0) {
244267
progress = Double(receivedSize) / Double(expectedSize)
245268
} else {
246269
progress = 0
247270
}
248271
DispatchQueue.main.async {
249-
self.indicatorStatus.progress = progress
272+
context.coordinator.imageLoading.progress = progress
250273
}
251274
self.imageHandler.progressBlock?(receivedSize, expectedSize)
252275
}) { (image, data, error, cacheType, finished, _) in
@@ -265,9 +288,9 @@ public struct AnimatedImage : PlatformViewRepresentable {
265288
}
266289
}
267290
}
268-
self.imageLoading.image = image
269-
self.indicatorStatus.isLoading = false
270-
self.indicatorStatus.progress = 1
291+
context.coordinator.imageLoading.image = image
292+
context.coordinator.imageLoading.isLoading = false
293+
context.coordinator.imageLoading.progress = 1
271294
if let image = image {
272295
self.imageConfiguration.placeholderView?.isHidden = true
273296
self.imageHandler.successBlock?(image, data, cacheType)
@@ -289,30 +312,30 @@ public struct AnimatedImage : PlatformViewRepresentable {
289312
func updateView(_ view: AnimatedImageViewWrapper, context: Context) {
290313
// Refresh image, imageModel is the Source of Truth, switch the type
291314
// Although we have Source of Truth, we can check the previous value, to avoid re-generate SDAnimatedImage, which is performance-cost.
292-
if let name = imageModel.name, name != imageLoading.imageName {
315+
if let name = imageModel.name, name != context.coordinator.imageLoading.imageName {
293316
#if os(macOS)
294317
let image = SDAnimatedImage(named: name, in: imageModel.bundle)
295318
#else
296319
let image = SDAnimatedImage(named: name, in: imageModel.bundle, compatibleWith: nil)
297320
#endif
298-
imageLoading.imageName = name
321+
context.coordinator.imageLoading.imageName = name
299322
view.wrapped.image = image
300-
} else if let data = imageModel.data, data != imageLoading.imageData {
323+
} else if let data = imageModel.data, data != context.coordinator.imageLoading.imageData {
301324
let image = SDAnimatedImage(data: data, scale: imageModel.scale)
302-
imageLoading.imageData = data
325+
context.coordinator.imageLoading.imageData = data
303326
view.wrapped.image = image
304327
} else if let url = imageModel.url {
305328
// Determine if image already been loaded and URL is match
306329
var shouldLoad: Bool
307-
if url != imageLoading.imageURL {
330+
if url != context.coordinator.imageLoading.imageURL {
308331
// Change the URL, need new loading
309332
shouldLoad = true
310-
imageLoading.imageURL = url
333+
context.coordinator.imageLoading.imageURL = url
311334
} else {
312335
// Same URL, check if already loaded
313-
if indicatorStatus.isLoading {
336+
if context.coordinator.imageLoading.isLoading {
314337
shouldLoad = false
315-
} else if let image = imageLoading.image {
338+
} else if let image = context.coordinator.imageLoading.image {
316339
shouldLoad = false
317340
view.wrapped.image = image
318341
} else {

0 commit comments

Comments
 (0)