Skip to content

Commit 5473784

Browse files
committed
Adopt the AnimatedImage with Indicator view modifier
1 parent be8e2c9 commit 5473784

File tree

4 files changed

+63
-14
lines changed

4 files changed

+63
-14
lines changed

SDWebImageSwiftUI/Classes/AnimatedImage.swift

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import SDWebImage
1111

1212
#if os(iOS) || os(tvOS) || os(macOS)
1313

14-
/// A coordinator object used for `AnimatedImage`native view bridge for UIKit/AppKit/WatchKit.
14+
/// A coordinator object used for `AnimatedImage`native view bridge for UIKit/AppKit.
1515
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
1616
public final class AnimatedImageCoordinator: NSObject {
1717

@@ -37,6 +37,14 @@ final class AnimatedImageModel : ObservableObject {
3737
@Published var scale: CGFloat = 1
3838
}
3939

40+
/// Loading Binding Object, only properties in this object can support changes from user with @State and refresh
41+
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
42+
final class AnimatedLoadingModel : ObservableObject, IndicatorReportable {
43+
@Published var image: PlatformImage? // loaded image, note when progressive loading, this will published multiple times with different partial image
44+
@Published var isLoading: Bool = false // whether network is loading or cache is querying, should only be used for indicator binding
45+
@Published var progress: Double = 0 // network progress, should only be used for indicator binding
46+
}
47+
4048
/// Completion Handler Binding Object, supports dynamic @State changes
4149
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
4250
final class AnimatedImageHandler: ObservableObject {
@@ -81,6 +89,7 @@ final class AnimatedImageConfiguration: ObservableObject {
8189
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
8290
public struct AnimatedImage : PlatformViewRepresentable {
8391
@ObservedObject var imageModel = AnimatedImageModel()
92+
@ObservedObject var imageLoading = AnimatedLoadingModel()
8493
@ObservedObject var imageHandler = AnimatedImageHandler()
8594
@ObservedObject var imageLayout = AnimatedImageLayout()
8695
@ObservedObject var imageConfiguration = AnimatedImageConfiguration()
@@ -193,18 +202,31 @@ public struct AnimatedImage : PlatformViewRepresentable {
193202
if currentOperation != nil {
194203
return
195204
}
205+
self.imageLoading.isLoading = true
196206
view.wrapped.sd_setImage(with: imageModel.url, placeholderImage: imageConfiguration.placeholder, options: imageModel.webOptions, context: imageModel.webContext, progress: { (receivedSize, expectedSize, _) in
207+
let progress: Double
208+
if (expectedSize > 0) {
209+
progress = Double(receivedSize) / Double(expectedSize)
210+
} else {
211+
progress = 0
212+
}
213+
DispatchQueue.main.async {
214+
self.imageLoading.progress = progress
215+
}
197216
self.imageHandler.progressBlock?(receivedSize, expectedSize)
198217
}) { (image, error, cacheType, _) in
199218
// This is a hack because of Xcode 11.3 bug, the @Published does not trigger another `updateUIView` call
200-
// Here I have to use UIKit API to triger the same effect (the window change implicitly cause re-render)
219+
// Here I have to use UIKit/AppKit API to triger the same effect (the window change implicitly cause re-render)
201220
if let hostingView = AnimatedImage.findHostingView(from: view) {
202221
#if os(macOS)
203222
hostingView.viewDidMoveToWindow()
204223
#else
205224
hostingView.didMoveToWindow()
206225
#endif
207226
}
227+
self.imageLoading.image = image
228+
self.imageLoading.isLoading = false
229+
self.imageLoading.progress = 1
208230
if let image = image {
209231
self.imageHandler.successBlock?(image, cacheType)
210232
} else {
@@ -704,7 +726,7 @@ extension AnimatedImage {
704726
}
705727
}
706728

707-
// Web Image convenience
729+
// Web Image convenience, based on UIKit/AppKit API
708730
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
709731
extension AnimatedImage {
710732

@@ -732,6 +754,23 @@ extension AnimatedImage {
732754
}
733755
}
734756

757+
// Indicator
758+
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
759+
extension AnimatedImage {
760+
761+
/// Associate a indicator when loading image with url
762+
/// - Parameter indicator: The indicator type, see `Indicator`
763+
public func indicator<T>(_ indicator: Indicator<T>) -> some View where T : View {
764+
return self.modifier(IndicatorViewModifier(reporter: self.imageLoading, indicator: indicator))
765+
}
766+
767+
/// Associate a indicator when loading image with url, convenient method with block
768+
/// - Parameter content: A view that describes the indicator.
769+
public func indicator<T>(@ViewBuilder content: @escaping (_ isAnimating: Binding<Bool>, _ progress: Binding<Double>) -> T) -> some View where T : View {
770+
return indicator(Indicator(content: content))
771+
}
772+
}
773+
735774
#if DEBUG
736775
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
737776
struct AnimatedImage_Previews : PreviewProvider {

SDWebImageSwiftUI/Classes/ImageManager.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import SwiftUI
1010
import SDWebImage
1111

1212
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
13-
class ImageManager : ObservableObject {
13+
class ImageManager : ObservableObject, IndicatorReportable {
1414
@Published var image: PlatformImage? // loaded image, note when progressive loading, this will published multiple times with different partial image
1515
@Published var isLoading: Bool = false // whether network is loading or cache is querying, should only be used for indicator binding
1616
@Published var progress: Double = 0 // network progress, should only be used for indicator binding
@@ -70,9 +70,7 @@ class ImageManager : ObservableObject {
7070
// So previous View struct call `onDisappear` and cancel the currentOperation
7171
return
7272
}
73-
if let image = image {
74-
self.image = image
75-
}
73+
self.image = image
7674
self.isIncremental = !finished
7775
if finished {
7876
self.isLoading = false

SDWebImageSwiftUI/Classes/Indicator/Indicator.swift

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,39 @@ public struct Indicator<T> where T : View {
1717
/// Create a indicator with builder
1818
/// - Parameter builder: A builder to build indicator
1919
/// - Parameter isAnimating: A Binding to control the animation. If image is during loading, the value is true, else (like start loading) the value is false.
20-
/// - Parameter progress: A Binding to control the progress during loading. Value between [0, 1]. If no progress can be reported, the value is 0.
20+
/// - Parameter progress: A Binding to control the progress during loading. Value between [0.0, 1.0]. If no progress can be reported, the value is 0.
2121
/// Associate a indicator when loading image with url
2222
public init(@ViewBuilder content: @escaping (_ isAnimating: Binding<Bool>, _ progress: Binding<Double>) -> T) {
2323
self.content = content
2424
}
2525
}
2626

27+
/// A protocol to report indicator progress
28+
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
29+
public protocol IndicatorReportable : ObservableObject {
30+
/// whether indicator is loading or not
31+
var isLoading: Bool { get set }
32+
/// indicator progress, should only be used for indicator binding, value between [0.0, 1.0]
33+
var progress: Double { get set }
34+
}
35+
2736
/// A implementation detail View Modifier with indicator
2837
/// SwiftUI View Modifier construced by using a internal View type which modify the `body`
2938
/// It use type system to represent the view hierarchy, and Swift `some View` syntax to hide the type detail for users
3039
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
31-
struct IndicatorViewModifier<T> : ViewModifier where T : View {
32-
@ObservedObject var imageManager: ImageManager
40+
public struct IndicatorViewModifier<T, V> : ViewModifier where T : View, V : IndicatorReportable {
41+
42+
/// The progress reporter
43+
@ObservedObject var reporter: V
3344

45+
/// The indicator
3446
var indicator: Indicator<T>
3547

36-
func body(content: Content) -> some View {
48+
public func body(content: Content) -> some View {
3749
ZStack {
3850
content
39-
if imageManager.isLoading {
40-
indicator.content($imageManager.isLoading, $imageManager.progress)
51+
if reporter.isLoading {
52+
indicator.content($reporter.isLoading, $reporter.progress)
4153
}
4254
}
4355
}

SDWebImageSwiftUI/Classes/WebImage.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ extension WebImage {
280280
/// Associate a indicator when loading image with url
281281
/// - Parameter indicator: The indicator type, see `Indicator`
282282
public func indicator<T>(_ indicator: Indicator<T>) -> some View where T : View {
283-
return self.modifier(IndicatorViewModifier(imageManager: imageManager, indicator: indicator))
283+
return self.modifier(IndicatorViewModifier(reporter: imageManager, indicator: indicator))
284284
}
285285

286286
/// Associate a indicator when loading image with url, convenient method with block

0 commit comments

Comments
 (0)