@@ -43,6 +43,11 @@ final class AnimatedLoadingModel : ObservableObject, IndicatorReportable {
43
43
@Published var image : PlatformImage ? // loaded image, note when progressive loading, this will published multiple times with different partial image
44
44
@Published var isLoading : Bool = false // whether network is loading or cache is querying, should only be used for indicator binding
45
45
@Published var progress : Double = 0 // network progress, should only be used for indicator binding
46
+
47
+ /// Used for loading status recording to avoid recursive `updateView`. There are 3 types of loading (Name/Data/URL)
48
+ @Published var imageName : String ?
49
+ @Published var imageData : Data ?
50
+ @Published var imageURL : URL ?
46
51
}
47
52
48
53
/// Completion Handler Binding Object, supports dynamic @State changes
@@ -201,12 +206,27 @@ public struct AnimatedImage : PlatformViewRepresentable {
201
206
}
202
207
#endif
203
208
204
- func loadImage( _ view: AnimatedImageViewWrapper , context: Context ) {
205
- let operationKey = NSStringFromClass ( type ( of: view. wrapped) )
206
- let currentOperation = view. wrapped. sd_imageLoadOperation ( forKey: operationKey)
207
- if currentOperation != nil {
208
- return
209
+ func setupIndicator( _ view: AnimatedImageViewWrapper , context: Context ) {
210
+ view. wrapped. sd_imageIndicator = imageConfiguration. indicator
211
+ view. wrapped. sd_imageTransition = imageConfiguration. transition
212
+ if let placeholderView = imageConfiguration. placeholderView {
213
+ placeholderView. removeFromSuperview ( )
214
+ placeholderView. isHidden = true
215
+ // Placeholder View should below the Indicator View
216
+ if let indicatorView = imageConfiguration. indicator? . indicatorView {
217
+ #if os(macOS)
218
+ view. wrapped. addSubview ( placeholderView, positioned: . below, relativeTo: indicatorView)
219
+ #else
220
+ view. wrapped. insertSubview ( placeholderView, belowSubview: indicatorView)
221
+ #endif
222
+ } else {
223
+ view. wrapped. addSubview ( placeholderView)
224
+ }
225
+ placeholderView. bindFrameToSuperviewBounds ( )
209
226
}
227
+ }
228
+
229
+ func loadImage( _ view: AnimatedImageViewWrapper , context: Context ) {
210
230
self . imageLoading. isLoading = true
211
231
let options = imageModel. webOptions
212
232
if options. contains ( . delayPlaceholder) {
@@ -228,15 +248,19 @@ public struct AnimatedImage : PlatformViewRepresentable {
228
248
}
229
249
self . imageHandler. progressBlock ? ( receivedSize, expectedSize)
230
250
} ) { ( image, data, error, cacheType, finished, _) in
231
- // This is a hack because of Xcode 11.3 bug, the @Published does not trigger another `updateUIView` call
232
- // Here I have to use UIKit/AppKit API to triger the same effect (the window change implicitly cause re-render)
233
- if let hostingView = AnimatedImage . findHostingView ( from: view) {
234
- if let _ = hostingView. window {
235
- #if os(macOS)
236
- hostingView. viewDidMoveToWindow ( )
237
- #else
238
- hostingView. didMoveToWindow ( )
239
- #endif
251
+ if #available( iOS 14 . 0 , macOS 11 . 0 , watchOS 7 . 0 , tvOS 14 . 0 , * ) {
252
+ // Do nothing. on iOS 14's SwiftUI, the @Published will always trigger another `updateUIView` call with new UIView instance.
253
+ } else {
254
+ // This is a hack because of iOS 13's SwiftUI bug, the @Published does not trigger another `updateUIView` call
255
+ // Here I have to use UIKit/AppKit API to triger the same effect (the window change implicitly cause re-render)
256
+ if let hostingView = AnimatedImage . findHostingView ( from: view) {
257
+ if let _ = hostingView. window {
258
+ #if os(macOS)
259
+ hostingView. viewDidMoveToWindow ( )
260
+ #else
261
+ hostingView. didMoveToWindow ( )
262
+ #endif
263
+ }
240
264
}
241
265
}
242
266
self . imageLoading. image = image
@@ -263,37 +287,40 @@ public struct AnimatedImage : PlatformViewRepresentable {
263
287
func updateView( _ view: AnimatedImageViewWrapper , context: Context ) {
264
288
// Refresh image, imageModel is the Source of Truth, switch the type
265
289
// Although we have Source of Truth, we can check the previous value, to avoid re-generate SDAnimatedImage, which is performance-cost.
266
- if let name = imageModel. name, name != view . wrapped . sd_imageName {
290
+ if let name = imageModel. name, name != imageLoading . imageName {
267
291
#if os(macOS)
268
292
let image = SDAnimatedImage ( named: name, in: imageModel. bundle)
269
293
#else
270
294
let image = SDAnimatedImage ( named: name, in: imageModel. bundle, compatibleWith: nil )
271
295
#endif
272
- view . wrapped . sd_imageName = name
296
+ imageLoading . imageName = name
273
297
view. wrapped. image = image
274
- } else if let data = imageModel. data, data != view . wrapped . sd_imageData {
298
+ } else if let data = imageModel. data, data != imageLoading . imageData {
275
299
let image = SDAnimatedImage ( data: data, scale: imageModel. scale)
276
- view . wrapped . sd_imageData = data
300
+ imageLoading . imageData = data
277
301
view. wrapped. image = image
278
- } else if let url = imageModel. url, url != view. wrapped. sd_imageURL {
279
- view. wrapped. sd_imageIndicator = imageConfiguration. indicator
280
- view. wrapped. sd_imageTransition = imageConfiguration. transition
281
- if let placeholderView = imageConfiguration. placeholderView {
282
- placeholderView. removeFromSuperview ( )
283
- placeholderView. isHidden = true
284
- // Placeholder View should below the Indicator View
285
- if let indicatorView = imageConfiguration. indicator? . indicatorView {
286
- #if os(macOS)
287
- view. wrapped. addSubview ( placeholderView, positioned: . below, relativeTo: indicatorView)
288
- #else
289
- view. wrapped. insertSubview ( placeholderView, belowSubview: indicatorView)
290
- #endif
302
+ } else if let url = imageModel. url {
303
+ // Determine if image already been loaded and URL is match
304
+ var shouldLoad : Bool
305
+ if url != imageLoading. imageURL {
306
+ // Change the URL, need new loading
307
+ shouldLoad = true
308
+ imageLoading. imageURL = url
309
+ } else {
310
+ // Same URL, check if already loaded
311
+ if imageLoading. isLoading {
312
+ shouldLoad = false
313
+ } else if let image = imageLoading. image {
314
+ shouldLoad = false
315
+ view. wrapped. image = image
291
316
} else {
292
- view . wrapped . addSubview ( placeholderView )
317
+ shouldLoad = true
293
318
}
294
- placeholderView. bindFrameToSuperviewBounds ( )
295
319
}
296
- loadImage ( view, context: context)
320
+ if shouldLoad {
321
+ setupIndicator ( view, context: context)
322
+ loadImage ( view, context: context)
323
+ }
297
324
}
298
325
299
326
#if os(macOS)
0 commit comments