Skip to content

Commit ab6d78a

Browse files
committed
Using the SwiftUI view modifer design pattern, to re-create indicator API. Now it will rerturn some View instead of WebImage
1 parent fa6907f commit ab6d78a

File tree

4 files changed

+46
-60
lines changed

4 files changed

+46
-60
lines changed

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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +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+
.resizable()
6566
.indicator { isAnimating, progress in
6667
ProgressBar(value: progress)
6768
.foregroundColor(.blue)
6869
.frame(maxHeight: 6)
6970
}
70-
.resizable()
7171
.scaledToFit()
7272
#endif
7373
}

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/WebImage.swift

Lines changed: 5 additions & 50 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,18 +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)
181138
}
182139

183140
/// Associate a indicator when loading image with url, convenient method with block
184141
/// - Parameter indicator: The indicator type, see `Indicator`
185-
public func indicator<T>(@ViewBuilder builder: @escaping (_ isAnimating: Binding<Bool>, _ progress: Binding<CGFloat>) -> T) -> WebImage where T : View {
186-
var result = self
187-
result.indicator = Indicator(builder: builder)
188-
return result
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))
189144
}
190145
}
191146

0 commit comments

Comments
 (0)