Skip to content

Commit ca22dc4

Browse files
authored
Merge pull request #84 from SDWebImage/support_indicator_modifier_animated_image
Support indicator modifier animated image
2 parents be8e2c9 + e47153c commit ca22dc4

File tree

5 files changed

+73
-14
lines changed

5 files changed

+73
-14
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,16 @@ var body: some View {
178178

179179
Note: `AnimatedImage` supports both image url or image data for animated image format. Which use the SDWebImage's [Animated ImageView](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) for internal implementation. Pay attention that since this base on UIKit/AppKit representable, some advanced SwiftUI layout and animation system may not work as expected. You may need UIKit/AppKit and Core Animation to modify the native view.
180180

181+
Note: `AnimatedImage` some methods like `.transition`, `.indicator` and `.aspectRatio` have the same naming as `SwiftUI.View` protocol methods. But the args receive the different type. This is because `AnimatedImage` supports to be used with UIKit/AppKit component and animation. If you find ambiguity, use full type declaration instead of the dot expression syntax.
182+
183+
```swift
184+
AnimatedImage(name: "animation2") // Just for showcase, don't mix them at the same time
185+
.indicator(SDWebImageProgressIndicator.default) // UIKit indicator component
186+
.indicator(Indicator.progress) // SwiftUI indicator component
187+
.transition(SDWebImageTransition.flipFromLeft) // UIKit animation transition
188+
.transition(AnyTransition.flipFromLeft) // SwiftUI animation transition
189+
```
190+
181191
### Which View to choose
182192

183193
Why we have two different View types here, is because of current SwiftUI limit. But we're aimed to provide best solution for all use cases.

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)