@@ -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 , * )
1616public 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 , * )
4250final 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 , * )
8290public 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 , * )
709731extension 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 , * )
737776struct AnimatedImage_Previews : PreviewProvider {
0 commit comments