Skip to content

Commit b62cf93

Browse files
authored
Merge pull request #31 from SDWebImage/feature_convenient_indicator_API
Refactory the indicator API on WebImage, using the same design pattern like SwiftUI, return some View
2 parents bd6a9a9 + 25304b6 commit b62cf93

File tree

8 files changed

+65
-70
lines changed

8 files changed

+65
-70
lines changed

Example/Podfile.lock

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ PODS:
88
- libwebp/mux (1.0.3):
99
- libwebp/demux
1010
- libwebp/webp (1.0.3)
11-
- SDWebImage (5.2.3):
12-
- SDWebImage/Core (= 5.2.3)
13-
- SDWebImage/Core (5.2.3)
14-
- SDWebImageSwiftUI (0.4.2):
11+
- SDWebImage (5.2.5):
12+
- SDWebImage/Core (= 5.2.5)
13+
- SDWebImage/Core (5.2.5)
14+
- SDWebImageSwiftUI (0.5.2):
1515
- SDWebImage (~> 5.1)
1616
- SDWebImageWebPCoder (0.2.5):
1717
- libwebp (~> 1.0)
@@ -33,8 +33,8 @@ EXTERNAL SOURCES:
3333

3434
SPEC CHECKSUMS:
3535
libwebp: 057912d6d0abfb6357d8bb05c0ea470301f5d61e
36-
SDWebImage: 46a7f73228f84ce80990c786e4372cf4db5875ce
37-
SDWebImageSwiftUI: b91be76ecb0cdf74c18f6cd92aae8f19a9ded02d
36+
SDWebImage: 4eabf2fa6695c95c47724214417a9c036c965e4a
37+
SDWebImageSwiftUI: 3a3e3aa707aeedf024af6f9c718177f681edf9de
3838
SDWebImageWebPCoder: 947093edd1349d820c40afbd9f42acb6cdecd987
3939

4040
PODFILE CHECKSUM: 3fb06a5173225e197f3a4bf2be7e5586a693257a

Example/SDWebImageSwiftUIDemo/ContentView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ struct ContentView: View {
9696
} else {
9797
#if os(macOS) || os(iOS) || os(tvOS)
9898
WebImage(url: URL(string:url))
99-
.indicator(.activity)
10099
.resizable()
100+
.indicator(.activity)
101101
.scaledToFit()
102102
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
103103
.animation(.easeInOut(duration: 0.5))

Example/SDWebImageSwiftUIDemo/DetailView.swift

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,17 @@ struct DetailView: View {
5757
} else {
5858
#if os(macOS) || os(iOS) || os(tvOS)
5959
WebImage(url: URL(string:url), options: [.progressiveLoad])
60-
.indicator(.progress)
6160
.resizable()
61+
.indicator(.progress)
6262
.scaledToFit()
6363
#else
6464
WebImage(url: URL(string:url), options: [.progressiveLoad])
65-
.indicator(
66-
Indicator { isAnimating, progress in
67-
ProgressBar(value: progress)
68-
.foregroundColor(.blue)
69-
.frame(maxHeight: 6)
70-
}
71-
)
7265
.resizable()
66+
.indicator { isAnimating, progress in
67+
ProgressBar(value: progress)
68+
.foregroundColor(.blue)
69+
.frame(maxHeight: 6)
70+
}
7371
.scaledToFit()
7472
#endif
7573
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ var body: some View {
7373
.onSuccess { image, cacheType in
7474
// Success
7575
}
76-
.indicator(.activity) // Activity Indicator
7776
.resizable()
77+
.indicator(.activity) // Activity Indicator
7878
.scaledToFit()
7979
.frame(width: 300, height: 300, alignment: .center)
8080
}

SDWebImageSwiftUI/Classes/ImageViewWrapper.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public class ProgressIndicatorWrapper : PlatformView {
6565
#if os(macOS)
6666
public override func layout() {
6767
super.layout()
68-
wrapped.setFrameOrigin(CGPoint(x: (self.bounds.width - wrapped.frame.width) / 2, y: (self.bounds.height - wrapped.frame.height) / 2))
68+
wrapped.setFrameOrigin(CGPoint(x: round(self.bounds.width - wrapped.frame.width) / 2, y: round(self.bounds.height - wrapped.frame.height) / 2))
6969
}
7070
#else
7171
public override func layoutSubviews() {

SDWebImageSwiftUI/Classes/Indicator/Indicator.swift

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,52 @@ import Foundation
1010
import SwiftUI
1111

1212
/// A type to build the indicator
13-
public struct Indicator {
14-
var builder: (Binding<Bool>, Binding<CGFloat>) -> AnyView
13+
public struct Indicator<T> where T : View {
14+
var builder: (Binding<Bool>, Binding<CGFloat>) -> T
1515

1616
/// Create a indicator with builder
1717
/// - Parameter builder: A builder to build indicator
1818
/// - 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.
1919
/// - Parameter progress: A Binding to control the progress during loading. If no progress can be reported, the value is 0.
2020
/// Associate a indicator when loading image with url
21-
public init<T>(@ViewBuilder builder: @escaping (_ isAnimating: Binding<Bool>, _ progress: Binding<CGFloat>) -> T) where T : View {
22-
self.builder = { isAnimating, progress in
23-
AnyView(builder(isAnimating, progress))
21+
public init(@ViewBuilder builder: @escaping (_ isAnimating: Binding<Bool>, _ progress: Binding<CGFloat>) -> T) {
22+
self.builder = builder
23+
}
24+
}
25+
26+
/// A implementation detail View with indicator
27+
/// SwiftUI View Modifier construced by using a internal View type which modify the `body`
28+
/// It use type system to represent the view hierarchy, and Swift `some View` syntax to hide the type detail for users
29+
struct IndicatorView<T, Content> : View where T : View, Content : View {
30+
@ObservedObject var imageManager: ImageManager
31+
32+
var indicator: Indicator<T>
33+
var content: Content
34+
35+
var body: some View {
36+
if (imageManager.image != nil) && !imageManager.isLoading {
37+
// Disable Indiactor
38+
return AnyView(content)
39+
} else {
40+
// Enable indicator
41+
return AnyView(
42+
ZStack {
43+
content
44+
indicator.builder($imageManager.isLoading, $imageManager.progress)
45+
}
46+
)
2447
}
2548
}
49+
50+
public init(_ view: Content, indicator: Indicator<T>, imageManager: ImageManager) {
51+
self.content = view
52+
self.indicator = indicator
53+
self.imageManager = imageManager
54+
}
2655
}
2756

2857
#if os(macOS) || os(iOS) || os(tvOS)
29-
extension Indicator {
58+
extension Indicator where T == ActivityIndicator {
3059
/// Activity Indicator
3160
public static var activity: Indicator {
3261
Indicator { isAnimating, _ in
@@ -41,7 +70,9 @@ extension Indicator {
4170
ActivityIndicator(isAnimating, style: style)
4271
}
4372
}
44-
73+
}
74+
75+
extension Indicator where T == ProgressIndicator {
4576
/// Progress Indicator
4677
public static var progress: Indicator {
4778
Indicator { isAnimating, progress in

SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ public struct ProgressIndicator: PlatformViewRepresentable {
6767
view.style = .bar
6868
view.isDisplayedWhenStopped = false
6969
view.controlSize = .small
70+
view.frame = CGRect(x: 0, y: 0, width: 160, height: 0) // Width from `UIProgressView` default width
71+
view.sizeToFit()
72+
view.autoresizingMask = [.maxXMargin, .minXMargin, .maxYMargin, .minYMargin]
7073
return nsView
7174
}
7275

SDWebImageSwiftUI/Classes/WebImage.swift

Lines changed: 9 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,8 @@ public struct WebImage : View {
1616
var context: [SDWebImageContextOption : Any]?
1717

1818
var configurations: [(Image) -> Image] = []
19-
var indicator: Indicator?
2019

2120
@ObservedObject var imageManager: ImageManager
22-
@State var progress: CGFloat = 0
23-
@State var isLoading: Bool = false
24-
var isFinished: Bool {
25-
!isLoading && (imageManager.image != nil)
26-
}
2721

2822
/// Create a web image with url, placeholder, custom options and context.
2923
/// - Parameter url: The image url
@@ -52,7 +46,7 @@ public struct WebImage : View {
5246
// this can ensure we load the image, SDWebImage take care of the duplicated query
5347
self.imageManager.load()
5448
}
55-
let view = configurations.reduce(image) { (previous, configuration) in
49+
return configurations.reduce(image) { (previous, configuration) in
5650
configuration(previous)
5751
}
5852
.onAppear {
@@ -63,41 +57,6 @@ public struct WebImage : View {
6357
.onDisappear {
6458
self.imageManager.cancel()
6559
}
66-
// Convert Combine.Publisher to Binding
67-
.onReceive(imageManager.$isLoading) { isLoading in
68-
// only Apple Watch complain that "Modifying state during view update, this will cause undefined behavior."
69-
// Use dispatch to workaround, Thanks Apple :)
70-
#if os(watchOS)
71-
DispatchQueue.main.async {
72-
self.isLoading = isLoading
73-
}
74-
#else
75-
self.isLoading = isLoading
76-
#endif
77-
}
78-
.onReceive(imageManager.$progress) { progress in
79-
#if os(watchOS)
80-
DispatchQueue.main.async {
81-
self.progress = progress
82-
}
83-
#else
84-
self.progress = progress
85-
#endif
86-
}
87-
if let indicator = indicator {
88-
if isFinished {
89-
return AnyView(view)
90-
} else {
91-
return AnyView(
92-
ZStack {
93-
view
94-
indicator.builder($isLoading, $progress)
95-
}
96-
)
97-
}
98-
} else {
99-
return AnyView(view)
100-
}
10160
}
10261
}
10362

@@ -174,10 +133,14 @@ extension WebImage {
174133

175134
/// Associate a indicator when loading image with url
176135
/// - Parameter indicator: The indicator type, see `Indicator`
177-
public func indicator(_ indicator: Indicator?) -> WebImage {
178-
var result = self
179-
result.indicator = indicator
180-
return result
136+
public func indicator<T>(_ indicator: Indicator<T>) -> some View where T : View {
137+
return IndicatorView(self, indicator: indicator, imageManager: imageManager)
138+
}
139+
140+
/// Associate a indicator when loading image with url, convenient method with block
141+
/// - Parameter indicator: The indicator type, see `Indicator`
142+
public func indicator<T>(@ViewBuilder builder: @escaping (_ isAnimating: Binding<Bool>, _ progress: Binding<CGFloat>) -> T) -> some View where T : View {
143+
return indicator(Indicator(builder: builder))
181144
}
182145
}
183146

0 commit comments

Comments
 (0)