Skip to content

Commit 6dc0002

Browse files
authored
Merge pull request #26 from SDWebImage/feature_indicator_transition
Feature: Support indicator and transition on `WebImage/AnimatedImage`
2 parents c99f5c6 + 63b6da1 commit 6dc0002

File tree

14 files changed

+409
-36
lines changed

14 files changed

+409
-36
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Carthage/Build
3535
# `pod install` in .travis.yml
3636
#
3737
Pods/
38+
Podfile.lock
3839

3940
# SwiftPM
4041
.build

Example/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ PODS:
1111
- SDWebImage (5.2.3):
1212
- SDWebImage/Core (= 5.2.3)
1313
- SDWebImage/Core (5.2.3)
14-
- SDWebImageSwiftUI (0.4.1):
14+
- SDWebImageSwiftUI (0.4.2):
1515
- SDWebImage (~> 5.1)
1616
- SDWebImageWebPCoder (0.2.5):
1717
- libwebp (~> 1.0)
@@ -34,7 +34,7 @@ EXTERNAL SOURCES:
3434
SPEC CHECKSUMS:
3535
libwebp: 057912d6d0abfb6357d8bb05c0ea470301f5d61e
3636
SDWebImage: 46a7f73228f84ce80990c786e4372cf4db5875ce
37-
SDWebImageSwiftUI: 15eeed7470ba9cd64fa7e8dddd62e12df58d07f3
37+
SDWebImageSwiftUI: b91be76ecb0cdf74c18f6cd92aae8f19a9ded02d
3838
SDWebImageWebPCoder: 947093edd1349d820c40afbd9f42acb6cdecd987
3939

4040
PODFILE CHECKSUM: 3fb06a5173225e197f3a4bf2be7e5586a693257a

Example/SDWebImageSwiftUIDemo/ContentView.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,34 @@ struct ContentView: View {
8080
NavigationLink(destination: DetailView(url: url, animated: self.animated)) {
8181
HStack {
8282
if self.animated {
83+
#if os(macOS) || os(iOS) || os(tvOS)
8384
AnimatedImage(url: URL(string:url))
85+
.indicator(SDWebImageActivityIndicator.medium)
86+
.transition(.fade)
8487
.resizable()
8588
.scaledToFit()
8689
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
90+
#else
91+
AnimatedImage(url: URL(string:url))
92+
.resizable()
93+
.scaledToFit()
94+
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
95+
#endif
8796
} else {
97+
#if os(macOS) || os(iOS) || os(tvOS)
98+
WebImage(url: URL(string:url))
99+
.indicator(.activity)
100+
.resizable()
101+
.scaledToFit()
102+
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
103+
.animation(.easeInOut(duration: 0.5))
104+
.transition(.opacity)
105+
#else
88106
WebImage(url: URL(string:url))
89107
.resizable()
90108
.scaledToFit()
91109
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
110+
#endif
92111
}
93112
Text((url as NSString).lastPathComponent)
94113
}

Example/SDWebImageSwiftUIDemo/DetailView.swift

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,10 @@ import SDWebImageSwiftUI
1212
struct DetailView: View {
1313
let url: String
1414
let animated: Bool
15-
@State var progress: CGFloat = 1
1615
@State var isAnimating: Bool = true
1716

1817
var body: some View {
1918
VStack {
20-
HStack {
21-
ProgressBar(value: $progress)
22-
.foregroundColor(.blue)
23-
.frame(maxHeight: 6)
24-
}
25-
Spacer()
2619
#if os(iOS) || os(tvOS)
2720
if animated {
2821
contentView()
@@ -45,35 +38,40 @@ struct DetailView: View {
4538
contentView()
4639
}
4740
#endif
48-
Spacer()
4941
}
5042
}
5143

5244
func contentView() -> some View {
5345
HStack {
5446
if animated {
47+
#if os(macOS) || os(iOS) || os(tvOS)
48+
AnimatedImage(url: URL(string:url), options: [.progressiveLoad], isAnimating: $isAnimating)
49+
.indicator(SDWebImageProgressIndicator.default)
50+
.resizable()
51+
.scaledToFit()
52+
#else
5553
AnimatedImage(url: URL(string:url), options: [.progressiveLoad], isAnimating: $isAnimating)
56-
.onProgress { receivedSize, expectedSize in
57-
// SwiftUI engine itself ensure the main queue dispatch
58-
if (expectedSize > 0) {
59-
self.progress = CGFloat(receivedSize) / CGFloat(expectedSize)
60-
} else {
61-
self.progress = 1
62-
}
63-
}
6454
.resizable()
6555
.scaledToFit()
56+
#endif
6657
} else {
58+
#if os(macOS) || os(iOS) || os(tvOS)
6759
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
60+
.indicator(.progress)
61+
.resizable()
62+
.scaledToFit()
63+
#else
64+
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)
7370
}
74-
}
71+
)
7572
.resizable()
7673
.scaledToFit()
74+
#endif
7775
}
7876
}
7977
}

Example/SDWebImageSwiftUIDemo/ProgressBar.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ public struct ProgressBar: View {
1414

1515
public var body: some View {
1616
GeometryReader { geometry in
17-
ZStack(alignment: .topLeading) {
18-
Capsule()
17+
ZStack(alignment: .leading) {
18+
Rectangle()
1919
.frame(width: geometry.size.width)
2020
.opacity(0.3)
2121
Rectangle()
2222
.frame(width: geometry.size.width * self.value)
23+
.opacity(0.6)
2324
}
2425
}
25-
.clipShape(Capsule())
26-
.opacity(self.value < 1 ? 1 : 0)
26+
.cornerRadius(2)
2727
}
2828
}

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,19 @@ let package = Package(
6161

6262
### Using `WebImage` to load network image
6363

64-
- [x] Supports the placeholder and detail options control for image loading as SDWebImage.
65-
- [x] Supports the success/failure/progress changes event for custom handling.
64+
- [x] Supports placeholder and detail options control for image loading as SDWebImage
65+
- [x] Supports success/failure/progress changes event for custom handling
66+
- [x] Supports indicator with activity/progress indicator and customization
6667

67-
Note: Unlike `UIImageView` in UIKit, SwiftUI's `Image` does not support animation. This `WebImage` using `Image` for internal implementation and supports static image format only.
68+
Note: This `WebImage` using `Image` for internal implementation, which is the best compatible for SwiftUI layout and animation system. But it supports static image format only, because unlike `UIImageView` in UIKit, SwiftUI's `Image` does not support animation.
6869

6970
```swift
7071
var body: some View {
7172
WebImage(url: URL(string: "https://nokiatech.github.io/heif/content/images/ski_jump_1440x960.heic"))
7273
.onSuccess { image, cacheType in
7374
// Success
7475
}
76+
.indicator(.activity) // Activity Indicator
7577
.resizable()
7678
.scaledToFit()
7779
.frame(width: 300, height: 300, alignment: .center)
@@ -88,6 +90,7 @@ var body: some View {
8890
.onFailure { error in
8991
// Error
9092
}
93+
.transition(.fade) // Fade Transition
9194
.scaledToFit()
9295
// Data
9396
AnimatedImage(data: try! Data(contentsOf: URL(fileURLWithPath: "/tmp/foo.webp")))
@@ -101,9 +104,10 @@ var body: some View {
101104

102105
- [x] Supports network image as well as local data and bundle image
103106
- [x] Supports animation control using the SwiftUI Binding
104-
- [x] Supports advanced control like loop count, incremental load, buffer size.
107+
- [x] Supports indicator and transition powered by SDWebImage and CoreAnimation
108+
- [x] Supports advanced control like loop count, incremental load, buffer size
105109

106-
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.
110+
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, if you need advanced customized layout and animation, you need CoreAnimation to help.
107111

108112
Note: From v0.4.0, `AnimatedImage` supports watchOS as well. However, it's not backed by SDWebImage's [Animated ImageView](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) like iOS/tvOS/macOS. It use some tricks and hacks because of the limitation on current Apple's API. It also use Image/IO decoding system, which means it supports GIF and APNG format only, but not external format like WebP.
109113

SDWebImageSwiftUI.xcodeproj/project.pbxproj

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@
1515
324F61CC235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */; };
1616
324F61CD235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */; };
1717
324F61CE235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */; };
18+
326B84822363350C0011BDFB /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B84812363350C0011BDFB /* Indicator.swift */; };
19+
326B84832363350C0011BDFB /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B84812363350C0011BDFB /* Indicator.swift */; };
20+
326B84842363350C0011BDFB /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B84812363350C0011BDFB /* Indicator.swift */; };
21+
326B84852363350C0011BDFB /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B84812363350C0011BDFB /* Indicator.swift */; };
22+
326B8487236335110011BDFB /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B8486236335110011BDFB /* ActivityIndicator.swift */; };
23+
326B8488236335110011BDFB /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B8486236335110011BDFB /* ActivityIndicator.swift */; };
24+
326B8489236335110011BDFB /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B8486236335110011BDFB /* ActivityIndicator.swift */; };
25+
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 */; };
1830
326E480A23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
1931
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
2032
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
@@ -99,6 +111,9 @@
99111
/* Begin PBXFileReference section */
100112
324F61C5235E07EC003973B8 /* SDAnimatedImageInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDAnimatedImageInterface.h; sourceTree = "<group>"; };
101113
324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDAnimatedImageInterface.m; sourceTree = "<group>"; };
114+
326B84812363350C0011BDFB /* Indicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Indicator.swift; sourceTree = "<group>"; };
115+
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>"; };
102117
326E480923431C0F00C633E9 /* ImageViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewWrapper.swift; sourceTree = "<group>"; };
103118
32C43DCC22FD540D00BE87F5 /* SDWebImageSwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SDWebImageSwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
104119
32C43DDC22FD54C600BE87F5 /* ImageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageManager.swift; sourceTree = "<group>"; };
@@ -161,6 +176,16 @@
161176
path = ObjC;
162177
sourceTree = "<group>";
163178
};
179+
326099472362E09E006EBB22 /* Indicator */ = {
180+
isa = PBXGroup;
181+
children = (
182+
326B84812363350C0011BDFB /* Indicator.swift */,
183+
326B8486236335110011BDFB /* ActivityIndicator.swift */,
184+
326B848B236335400011BDFB /* ProgressIndicator.swift */,
185+
);
186+
path = Indicator;
187+
sourceTree = "<group>";
188+
};
164189
32C43DC222FD540D00BE87F5 = {
165190
isa = PBXGroup;
166191
children = (
@@ -194,6 +219,7 @@
194219
32C43DDB22FD54C600BE87F5 /* Classes */ = {
195220
isa = PBXGroup;
196221
children = (
222+
326099472362E09E006EBB22 /* Indicator */,
197223
324F61C4235E07EC003973B8 /* ObjC */,
198224
32C43DDC22FD54C600BE87F5 /* ImageManager.swift */,
199225
32C43DDE22FD54C600BE87F5 /* WebImage.swift */,
@@ -418,8 +444,11 @@
418444
buildActionMask = 2147483647;
419445
files = (
420446
32C43E1722FD583700BE87F5 /* WebImage.swift in Sources */,
447+
326B848C236335400011BDFB /* ProgressIndicator.swift in Sources */,
448+
326B84822363350C0011BDFB /* Indicator.swift in Sources */,
421449
32C43E3222FD5DE100BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
422450
326E480A23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
451+
326B8487236335110011BDFB /* ActivityIndicator.swift in Sources */,
423452
32C43E1622FD583700BE87F5 /* ImageManager.swift in Sources */,
424453
32C43E1822FD583700BE87F5 /* AnimatedImage.swift in Sources */,
425454
324F61CB235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */,
@@ -431,8 +460,11 @@
431460
buildActionMask = 2147483647;
432461
files = (
433462
32C43E1A22FD583700BE87F5 /* WebImage.swift in Sources */,
463+
326B848D236335400011BDFB /* ProgressIndicator.swift in Sources */,
464+
326B84832363350C0011BDFB /* Indicator.swift in Sources */,
434465
32C43E3322FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
435466
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
467+
326B8488236335110011BDFB /* ActivityIndicator.swift in Sources */,
436468
32C43E1922FD583700BE87F5 /* ImageManager.swift in Sources */,
437469
32C43E1B22FD583700BE87F5 /* AnimatedImage.swift in Sources */,
438470
324F61CC235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */,
@@ -444,8 +476,11 @@
444476
buildActionMask = 2147483647;
445477
files = (
446478
32C43E1D22FD583800BE87F5 /* WebImage.swift in Sources */,
479+
326B848E236335400011BDFB /* ProgressIndicator.swift in Sources */,
480+
326B84842363350C0011BDFB /* Indicator.swift in Sources */,
447481
32C43E3422FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
448482
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
483+
326B8489236335110011BDFB /* ActivityIndicator.swift in Sources */,
449484
32C43E1C22FD583800BE87F5 /* ImageManager.swift in Sources */,
450485
32C43E1E22FD583800BE87F5 /* AnimatedImage.swift in Sources */,
451486
324F61CD235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */,
@@ -457,8 +492,11 @@
457492
buildActionMask = 2147483647;
458493
files = (
459494
32C43E2022FD583800BE87F5 /* WebImage.swift in Sources */,
495+
326B848F236335400011BDFB /* ProgressIndicator.swift in Sources */,
496+
326B84852363350C0011BDFB /* Indicator.swift in Sources */,
460497
32C43E3522FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
461498
326E480D23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
499+
326B848A236335110011BDFB /* ActivityIndicator.swift in Sources */,
462500
32C43E1F22FD583800BE87F5 /* ImageManager.swift in Sources */,
463501
32C43E2122FD583800BE87F5 /* AnimatedImage.swift in Sources */,
464502
324F61CE235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */,

SDWebImageSwiftUI/Classes/AnimatedImage.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ final class AnimatedImageConfiguration: ObservableObject {
3636
@Published var incrementalLoad: Bool?
3737
@Published var maxBufferSize: UInt?
3838
@Published var customLoopCount: Int?
39+
#if os(macOS) || os(iOS) || os(tvOS)
40+
// These configurations only useful for web image loading
41+
@Published var indicator: SDWebImageIndicator?
42+
@Published var transition: SDWebImageTransition?
43+
#endif
3944
}
4045

4146
// Convenient
@@ -203,6 +208,10 @@ public struct AnimatedImage : PlatformViewRepresentable {
203208
#endif
204209
} else {
205210
if let url = url {
211+
#if os(macOS) || os(iOS) || os(tvOS)
212+
view.wrapped.sd_imageIndicator = imageConfiguration.indicator
213+
view.wrapped.sd_imageTransition = imageConfiguration.transition
214+
#endif
206215
loadImage(view, url: url)
207216
}
208217
}
@@ -545,6 +554,28 @@ extension AnimatedImage {
545554
}
546555
}
547556

557+
#if os(macOS) || os(iOS) || os(tvOS)
558+
// Web Image convenience
559+
extension AnimatedImage {
560+
561+
/// Associate a indicator when loading image with url
562+
/// - Note: If you do not need indicator, specify nil. Defaults to nil
563+
/// - Parameter indicator: indicator, see more in `SDWebImageIndicator`
564+
public func indicator(_ indicator: SDWebImageIndicator?) -> AnimatedImage {
565+
imageConfiguration.indicator = indicator
566+
return self
567+
}
568+
569+
/// Associate a transition when loading image with url
570+
/// - Note: If you specify nil, do not do transition. Defautls to nil.
571+
/// - Parameter transition: transition, see more in `SDWebImageTransition`
572+
public func transition(_ transition: SDWebImageTransition?) -> AnimatedImage {
573+
imageConfiguration.transition = transition
574+
return self
575+
}
576+
}
577+
#endif
578+
548579
#if DEBUG
549580
struct AnimatedImage_Previews : PreviewProvider {
550581
static var previews: some View {

0 commit comments

Comments
 (0)