Skip to content

Commit 40c5644

Browse files
committed
Add activity indicator implementation as well, using UIProgressView/NSProgressIndicator
1 parent 6a2eb02 commit 40c5644

File tree

7 files changed

+133
-10
lines changed

7 files changed

+133
-10
lines changed

Example/SDWebImageSwiftUIDemo/DetailView.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,8 @@ struct DetailView: View {
6565
.scaledToFit()
6666
} else {
6767
WebImage(url: URL(string:url), options: [.progressiveLoad])
68-
.onProgress { receivedSize, expectedSize in
69-
if (expectedSize > 0) {
70-
self.progress = CGFloat(receivedSize) / CGFloat(expectedSize)
71-
} else {
72-
self.progress = 1
73-
}
68+
.indicator { isAnimating, progress in
69+
ProgressIndicator(isAnimating, progress: progress)
7470
}
7571
.resizable()
7672
.scaledToFit()

SDWebImageSwiftUI.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
326B8488236335110011BDFB /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B8486236335110011BDFB /* ActivityIndicator.swift */; };
2424
326B8489236335110011BDFB /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B8486236335110011BDFB /* ActivityIndicator.swift */; };
2525
326B848A236335110011BDFB /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B8486236335110011BDFB /* ActivityIndicator.swift */; };
26+
326B848C236335400011BDFB /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B848B236335400011BDFB /* ProgressIndicator.swift */; };
27+
326B848D236335400011BDFB /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B848B236335400011BDFB /* ProgressIndicator.swift */; };
28+
326B848E236335400011BDFB /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B848B236335400011BDFB /* ProgressIndicator.swift */; };
29+
326B848F236335400011BDFB /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B848B236335400011BDFB /* ProgressIndicator.swift */; };
2630
326E480A23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
2731
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
2832
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
@@ -109,6 +113,7 @@
109113
324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDAnimatedImageInterface.m; sourceTree = "<group>"; };
110114
326B84812363350C0011BDFB /* Indicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Indicator.swift; sourceTree = "<group>"; };
111115
326B8486236335110011BDFB /* ActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = "<group>"; };
116+
326B848B236335400011BDFB /* ProgressIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressIndicator.swift; sourceTree = "<group>"; };
112117
326E480923431C0F00C633E9 /* ImageViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewWrapper.swift; sourceTree = "<group>"; };
113118
32C43DCC22FD540D00BE87F5 /* SDWebImageSwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SDWebImageSwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
114119
32C43DDC22FD54C600BE87F5 /* ImageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageManager.swift; sourceTree = "<group>"; };
@@ -176,6 +181,7 @@
176181
children = (
177182
326B84812363350C0011BDFB /* Indicator.swift */,
178183
326B8486236335110011BDFB /* ActivityIndicator.swift */,
184+
326B848B236335400011BDFB /* ProgressIndicator.swift */,
179185
);
180186
path = Indicator;
181187
sourceTree = "<group>";
@@ -438,6 +444,7 @@
438444
buildActionMask = 2147483647;
439445
files = (
440446
32C43E1722FD583700BE87F5 /* WebImage.swift in Sources */,
447+
326B848C236335400011BDFB /* ProgressIndicator.swift in Sources */,
441448
326B84822363350C0011BDFB /* Indicator.swift in Sources */,
442449
32C43E3222FD5DE100BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
443450
326E480A23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
@@ -453,6 +460,7 @@
453460
buildActionMask = 2147483647;
454461
files = (
455462
32C43E1A22FD583700BE87F5 /* WebImage.swift in Sources */,
463+
326B848D236335400011BDFB /* ProgressIndicator.swift in Sources */,
456464
326B84832363350C0011BDFB /* Indicator.swift in Sources */,
457465
32C43E3322FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
458466
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
@@ -468,6 +476,7 @@
468476
buildActionMask = 2147483647;
469477
files = (
470478
32C43E1D22FD583800BE87F5 /* WebImage.swift in Sources */,
479+
326B848E236335400011BDFB /* ProgressIndicator.swift in Sources */,
471480
326B84842363350C0011BDFB /* Indicator.swift in Sources */,
472481
32C43E3422FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
473482
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
@@ -483,6 +492,7 @@
483492
buildActionMask = 2147483647;
484493
files = (
485494
32C43E2022FD583800BE87F5 /* WebImage.swift in Sources */,
495+
326B848F236335400011BDFB /* ProgressIndicator.swift in Sources */,
486496
326B84852363350C0011BDFB /* Indicator.swift in Sources */,
487497
32C43E3522FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
488498
326E480D23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,

SDWebImageSwiftUI/Classes/ImageManager.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class ImageManager : ObservableObject {
3939
guard let self = self else {
4040
return
4141
}
42-
self.progressBlock?(receivedSize, expectedSize)
4342
let progress: CGFloat
4443
if (expectedSize > 0) {
4544
progress = CGFloat(receivedSize) / CGFloat(expectedSize)
@@ -49,6 +48,7 @@ class ImageManager : ObservableObject {
4948
DispatchQueue.main.async {
5049
self.progress = progress
5150
}
51+
self.progressBlock?(receivedSize, expectedSize)
5252
}) { [weak self] (image, data, error, cacheType, finished, _) in
5353
guard let self = self else {
5454
return
@@ -58,6 +58,7 @@ class ImageManager : ObservableObject {
5858
}
5959
if finished {
6060
self.isLoading = false
61+
self.progress = 1
6162
if let image = image {
6263
self.successBlock?(image, cacheType)
6364
} else {

SDWebImageSwiftUI/Classes/ImageViewWrapper.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import SDWebImage
1111

1212
#if !os(watchOS)
1313

14-
// View Wrapper
14+
/// Use wrapper to solve tne `UIImageView`/`NSImageView` frame size become image size issue (SwiftUI's Bug)
1515
public class AnimatedImageViewWrapper : PlatformView {
1616
var wrapped = SDAnimatedImageView()
1717
var interpolationQuality = CGInterpolationQuality.default
@@ -54,4 +54,36 @@ public class AnimatedImageViewWrapper : PlatformView {
5454
}
5555
}
5656

57+
/// Use wrapper to solve the `UIProgressView`/`NSProgressIndicator` frame origin NaN crash (SwiftUI's bug)
58+
public class ProgressIndicatorWrapper : PlatformView {
59+
#if os(macOS)
60+
var wrapped = NSProgressIndicator()
61+
#else
62+
var wrapped = UIProgressView(progressViewStyle: .default)
63+
#endif
64+
65+
#if os(macOS)
66+
public override func layout() {
67+
super.layout()
68+
wrapped.frame = self.bounds
69+
}
70+
#else
71+
public override func layoutSubviews() {
72+
super.layoutSubviews()
73+
wrapped.frame = self.bounds
74+
wrapped.center = self.center
75+
}
76+
#endif
77+
78+
public override init(frame frameRect: CGRect) {
79+
super.init(frame: frameRect)
80+
addSubview(wrapped)
81+
}
82+
83+
public required init?(coder: NSCoder) {
84+
super.init(coder: coder)
85+
addSubview(wrapped)
86+
}
87+
}
88+
5789
#endif

SDWebImageSwiftUI/Classes/Indicator/ActivityIndicator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
* file that was distributed with this source code.
77
*/
88

9-
import Swift
109
import SwiftUI
1110

11+
/// An activity indicator (system style)
1212
public struct ActivityIndicator: PlatformViewRepresentable {
1313
@Binding var isAnimating: Bool
1414

15-
public init(_ isAnimating: Binding<Bool> = .constant(true)) {
15+
public init(_ isAnimating: Binding<Bool>) {
1616
self._isAnimating = isAnimating
1717
}
1818

SDWebImageSwiftUI/Classes/Indicator/Indicator.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import Foundation
1010
import SwiftUI
1111

12+
/// A container view to hold the indicator builder
1213
public struct Indicator : View {
1314
var builder: (Binding<Bool>, Binding<CGFloat>) -> AnyView
1415
public typealias Body = Never
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* This file is part of the SDWebImage package.
3+
* (c) DreamPiggy <lizhuoli1126@126.com>
4+
*
5+
* For the full copyright and license information, please view the LICENSE
6+
* file that was distributed with this source code.
7+
*/
8+
9+
import SwiftUI
10+
11+
/// A progress bar indicator (system style)
12+
public struct ProgressIndicator: PlatformViewRepresentable {
13+
@Binding var isAnimating: Bool
14+
@Binding var progress: CGFloat
15+
16+
public init(_ isAnimating: Binding<Bool>, progress: Binding<CGFloat>) {
17+
self._isAnimating = isAnimating
18+
self._progress = progress
19+
}
20+
21+
#if os(macOS)
22+
public typealias NSViewType = ProgressIndicatorWrapper
23+
#elseif os(iOS) || os(tvOS)
24+
public typealias UIViewType = ProgressIndicatorWrapper
25+
#endif
26+
27+
#if os(iOS) || os(tvOS)
28+
public func makeUIView(context: UIViewRepresentableContext<ProgressIndicator>) -> ProgressIndicatorWrapper {
29+
let uiView = ProgressIndicatorWrapper()
30+
let view = uiView.wrapped
31+
view.progressViewStyle = .default
32+
return uiView
33+
}
34+
35+
public func updateUIView(_ uiView: ProgressIndicatorWrapper, context: UIViewRepresentableContext<ProgressIndicator>) {
36+
let view = uiView.wrapped
37+
if isAnimating {
38+
view.setProgress(Float(progress), animated: true)
39+
} else {
40+
if progress == 0 {
41+
view.isHidden = false
42+
view.progress = 0
43+
} else {
44+
view.isHidden = true
45+
view.progress = 1
46+
}
47+
}
48+
}
49+
#endif
50+
51+
#if os(macOS)
52+
public func makeNSView(context: NSViewRepresentableContext<ProgressIndicator>) -> ProgressIndicatorWrapper {
53+
let nsView = ProgressIndicatorWrapper()
54+
let view = nsView.wrapped
55+
view.style = .bar
56+
view.isDisplayedWhenStopped = false
57+
view.controlSize = .small
58+
return nsView
59+
}
60+
61+
public func updateNSView(_ nsView: ProgressIndicatorWrapper, context: NSViewRepresentableContext<ProgressIndicator>) {
62+
let view = nsView.wrapped
63+
if isAnimating {
64+
view.isIndeterminate = false
65+
view.doubleValue = Double(progress) * 100
66+
view.startAnimation(nil)
67+
} else {
68+
if progress == 0 {
69+
view.isHidden = false
70+
view.isIndeterminate = true
71+
view.doubleValue = 0
72+
view.stopAnimation(nil)
73+
} else {
74+
view.isHidden = true
75+
view.isIndeterminate = false
76+
view.doubleValue = 100
77+
view.stopAnimation(nil)
78+
}
79+
}
80+
}
81+
82+
#endif
83+
}

0 commit comments

Comments
 (0)