Skip to content

Commit 42fdca6

Browse files
authored
Merge pull request #91 from SDWebImage/feature_delayPlaceholder_webimage
Supports the `delayPlaceholder` for WebImage
2 parents ca9922c + b084945 commit 42fdca6

File tree

6 files changed

+107
-38
lines changed

6 files changed

+107
-38
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "wifi.exclamationmark.svg",
5+
"idiom" : "universal",
6+
"scale" : "1x"
7+
},
8+
{
9+
"idiom" : "universal",
10+
"scale" : "2x"
11+
},
12+
{
13+
"idiom" : "universal",
14+
"scale" : "3x"
15+
}
16+
],
17+
"info" : {
18+
"author" : "xcode",
19+
"version" : 1
20+
},
21+
"properties" : {
22+
"preserves-vector-representation" : true
23+
}
24+
}
Loading

Example/SDWebImageSwiftUIDemo/ContentView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class UserSettings: ObservableObject {
2222
// watchOS does not provide built-in indicator, use Espera's custom indicator
2323
extension Indicator where T == LoadingFlowerView {
2424
/// Activity Indicator
25-
public static var activity: Indicator {
25+
static var activity: Indicator {
2626
Indicator { isAnimating, _ in
2727
LoadingFlowerView()
2828
}
@@ -31,7 +31,7 @@ extension Indicator where T == LoadingFlowerView {
3131

3232
extension Indicator where T == StretchProgressView {
3333
/// Progress Indicator
34-
public static var progress: Indicator {
34+
static var progress: Indicator {
3535
Indicator { isAnimating, progress in
3636
StretchProgressView(progress: progress)
3737
}

Example/SDWebImageSwiftUIDemo/DetailView.swift

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,31 @@
99
import SwiftUI
1010
import SDWebImageSwiftUI
1111

12+
// Placeholder when image load failed (with `.delayPlaceholder`)
13+
#if !os(watchOS)
14+
extension PlatformImage {
15+
static var wifiExclamationmark: PlatformImage {
16+
#if os(macOS)
17+
return PlatformImage(named: "wifi.exclamationmark")!
18+
#else
19+
return PlatformImage(systemName: "wifi.exclamationmark")!.withTintColor(.label, renderingMode: .alwaysOriginal)
20+
#endif
21+
}
22+
}
23+
#endif
24+
25+
extension Image {
26+
static var wifiExclamationmark: Image {
27+
#if os(macOS)
28+
return Image("wifi.exclamationmark")
29+
.resizable()
30+
#else
31+
return Image(systemName: "wifi.exclamationmark")
32+
.resizable()
33+
#endif
34+
}
35+
}
36+
1237
struct DetailView: View {
1338
let url: String
1439
let animated: Bool
@@ -86,19 +111,22 @@ struct DetailView: View {
86111
HStack {
87112
if animated {
88113
#if os(macOS) || os(iOS) || os(tvOS)
89-
AnimatedImage(url: URL(string:url), options: [.progressiveLoad], isAnimating: $isAnimating)
90-
.indicator(SDWebImageProgressIndicator.default)
114+
AnimatedImage(url: URL(string:url), options: [.progressiveLoad, .delayPlaceholder], isAnimating: $isAnimating)
91115
.resizable()
116+
.placeholder(.wifiExclamationmark)
117+
.indicator(SDWebImageProgressIndicator.default)
92118
.scaledToFit()
93119
#else
94-
WebImage(url: URL(string:url), options: [.progressiveLoad], isAnimating: $isAnimating)
120+
WebImage(url: URL(string:url), options: [.progressiveLoad, .delayPlaceholder], isAnimating: $isAnimating)
95121
.resizable()
122+
.placeholder(.wifiExclamationmark)
96123
.indicator(.progress)
97124
.scaledToFit()
98125
#endif
99126
} else {
100-
WebImage(url: URL(string:url), options: [.progressiveLoad])
127+
WebImage(url: URL(string:url), options: [.progressiveLoad, .delayPlaceholder])
101128
.resizable()
129+
.placeholder(.wifiExclamationmark)
102130
.indicator(.progress)
103131
.scaledToFit()
104132
}

README.md

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -110,20 +110,21 @@ let package = Package(
110110
```swift
111111
var body: some View {
112112
WebImage(url: URL(string: "https://nokiatech.github.io/heif/content/images/ski_jump_1440x960.heic"))
113-
.onSuccess { image, cacheType in
114-
// Success
115-
}
116-
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
117-
.placeholder(Image(systemName: "photo")) // Placeholder Image
118-
// Supports ViewBuilder as well
119-
.placeholder {
120-
Rectangle().foregroundColor(.gray)
121-
}
122-
.indicator(.activity) // Activity Indicator
123-
.animation(.easeInOut(duration: 0.5)) // Animation Duration
124-
.transition(.fade) // Fade Transition
125-
.scaledToFit()
126-
.frame(width: 300, height: 300, alignment: .center)
113+
// Supports options and context, like `.delayPlaceholder` to show placeholder only when error
114+
.onSuccess { image, cacheType in
115+
// Success
116+
}
117+
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
118+
.placeholder(Image(systemName: "photo")) // Placeholder Image
119+
// Supports ViewBuilder as well
120+
.placeholder {
121+
Rectangle().foregroundColor(.gray)
122+
}
123+
.indicator(.activity) // Activity Indicator
124+
.animation(.easeInOut(duration: 0.5)) // Animation Duration
125+
.transition(.fade) // Fade Transition
126+
.scaledToFit()
127+
.frame(width: 300, height: 300, alignment: .center)
127128
}
128129
```
129130

@@ -155,8 +156,8 @@ var body: some View {
155156
```swift
156157
var body: some View {
157158
Group {
158-
// Network
159-
AnimatedImage(url: URL(string: "https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif"), options: [.progressiveLoad]) // Progressive Load
159+
AnimatedImage(url: URL(string: "https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif"))
160+
// Supports options and context, like `.progressiveLoad` for progressive animation loading
160161
.onFailure { error in
161162
// Error
162163
}
@@ -187,11 +188,13 @@ Note: `AnimatedImage` supports both image url or image data for animated image f
187188
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.
188189

189190
```swift
190-
AnimatedImage(name: "animation2") // Just for showcase, don't mix them at the same time
191-
.indicator(SDWebImageProgressIndicator.default) // UIKit indicator component
192-
.indicator(Indicator.progress) // SwiftUI indicator component
193-
.transition(SDWebImageTransition.flipFromLeft) // UIKit animation transition
194-
.transition(AnyTransition.flipFromLeft) // SwiftUI animation transition
191+
var body: some View {
192+
AnimatedImage(name: "animation2") // Just for showcase, don't mix them at the same time
193+
.indicator(SDWebImageProgressIndicator.default) // UIKit indicator component
194+
.indicator(Indicator.progress) // SwiftUI indicator component
195+
.transition(SDWebImageTransition.flipFromLeft) // UIKit animation transition
196+
.transition(AnyTransition.flipFromLeft) // SwiftUI animation transition
197+
}
195198
```
196199

197200
### Which View to choose

SDWebImageSwiftUI/Classes/WebImage.swift

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,7 @@ public struct WebImage : View {
106106
}
107107
}
108108
} else {
109-
Group {
110-
if placeholder != nil {
111-
placeholder
112-
} else {
113-
// Should not use `EmptyView`, which does not respect to the container's frame modifier
114-
// Using a empty image instead for better compatible
115-
configurations.reduce(Image.empty) { (previous, configuration) in
116-
configuration(previous)
117-
}
118-
}
119-
}
109+
setupPlaceholder()
120110
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
121111
.onAppear {
122112
guard self.retryOnAppear else { return }
@@ -136,6 +126,29 @@ public struct WebImage : View {
136126
}
137127
}
138128

129+
func configure(image: Image) -> some View {
130+
// Should not use `EmptyView`, which does not respect to the container's frame modifier
131+
// Using a empty image instead for better compatible
132+
configurations.reduce(image) { (previous, configuration) in
133+
configuration(previous)
134+
}
135+
}
136+
137+
/// Placeholder View Support
138+
func setupPlaceholder() -> some View {
139+
// Don't use `Group` because it will trigger `.onAppear` and `.onDisappear` when condition view removed, treat placeholder as an entire component
140+
if let placeholder = placeholder {
141+
// If use `.delayPlaceholder`, the placeholder is applied after loading failed, hide during loading :)
142+
if imageManager.options.contains(.delayPlaceholder) && imageManager.isLoading {
143+
return AnyView(configure(image: Image.empty))
144+
} else {
145+
return placeholder
146+
}
147+
} else {
148+
return AnyView(configure(image: Image.empty))
149+
}
150+
}
151+
139152
/// Animated Image Support
140153
func setupPlayer(image: PlatformImage?) {
141154
if imagePlayer != nil {

0 commit comments

Comments
 (0)