Skip to content

Commit

Permalink
Merge pull request #91 from SDWebImage/feature_delayPlaceholder_webimage
Browse files Browse the repository at this point in the history
Supports the `delayPlaceholder` for WebImage
  • Loading branch information
dreampiggy authored Mar 29, 2020
2 parents ca9922c + b084945 commit 42fdca6
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"images" : [
{
"filename" : "wifi.exclamationmark.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions Example/SDWebImageSwiftUIDemo/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class UserSettings: ObservableObject {
// watchOS does not provide built-in indicator, use Espera's custom indicator
extension Indicator where T == LoadingFlowerView {
/// Activity Indicator
public static var activity: Indicator {
static var activity: Indicator {
Indicator { isAnimating, _ in
LoadingFlowerView()
}
Expand All @@ -31,7 +31,7 @@ extension Indicator where T == LoadingFlowerView {

extension Indicator where T == StretchProgressView {
/// Progress Indicator
public static var progress: Indicator {
static var progress: Indicator {
Indicator { isAnimating, progress in
StretchProgressView(progress: progress)
}
Expand Down
36 changes: 32 additions & 4 deletions Example/SDWebImageSwiftUIDemo/DetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,31 @@
import SwiftUI
import SDWebImageSwiftUI

// Placeholder when image load failed (with `.delayPlaceholder`)
#if !os(watchOS)
extension PlatformImage {
static var wifiExclamationmark: PlatformImage {
#if os(macOS)
return PlatformImage(named: "wifi.exclamationmark")!
#else
return PlatformImage(systemName: "wifi.exclamationmark")!.withTintColor(.label, renderingMode: .alwaysOriginal)
#endif
}
}
#endif

extension Image {
static var wifiExclamationmark: Image {
#if os(macOS)
return Image("wifi.exclamationmark")
.resizable()
#else
return Image(systemName: "wifi.exclamationmark")
.resizable()
#endif
}
}

struct DetailView: View {
let url: String
let animated: Bool
Expand Down Expand Up @@ -86,19 +111,22 @@ struct DetailView: View {
HStack {
if animated {
#if os(macOS) || os(iOS) || os(tvOS)
AnimatedImage(url: URL(string:url), options: [.progressiveLoad], isAnimating: $isAnimating)
.indicator(SDWebImageProgressIndicator.default)
AnimatedImage(url: URL(string:url), options: [.progressiveLoad, .delayPlaceholder], isAnimating: $isAnimating)
.resizable()
.placeholder(.wifiExclamationmark)
.indicator(SDWebImageProgressIndicator.default)
.scaledToFit()
#else
WebImage(url: URL(string:url), options: [.progressiveLoad], isAnimating: $isAnimating)
WebImage(url: URL(string:url), options: [.progressiveLoad, .delayPlaceholder], isAnimating: $isAnimating)
.resizable()
.placeholder(.wifiExclamationmark)
.indicator(.progress)
.scaledToFit()
#endif
} else {
WebImage(url: URL(string:url), options: [.progressiveLoad])
WebImage(url: URL(string:url), options: [.progressiveLoad, .delayPlaceholder])
.resizable()
.placeholder(.wifiExclamationmark)
.indicator(.progress)
.scaledToFit()
}
Expand Down
45 changes: 24 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,21 @@ let package = Package(
```swift
var body: some View {
WebImage(url: URL(string: "https://nokiatech.github.io/heif/content/images/ski_jump_1440x960.heic"))
.onSuccess { image, cacheType in
// Success
}
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
.placeholder(Image(systemName: "photo")) // Placeholder Image
// Supports ViewBuilder as well
.placeholder {
Rectangle().foregroundColor(.gray)
}
.indicator(.activity) // Activity Indicator
.animation(.easeInOut(duration: 0.5)) // Animation Duration
.transition(.fade) // Fade Transition
.scaledToFit()
.frame(width: 300, height: 300, alignment: .center)
// Supports options and context, like `.delayPlaceholder` to show placeholder only when error
.onSuccess { image, cacheType in
// Success
}
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
.placeholder(Image(systemName: "photo")) // Placeholder Image
// Supports ViewBuilder as well
.placeholder {
Rectangle().foregroundColor(.gray)
}
.indicator(.activity) // Activity Indicator
.animation(.easeInOut(duration: 0.5)) // Animation Duration
.transition(.fade) // Fade Transition
.scaledToFit()
.frame(width: 300, height: 300, alignment: .center)
}
```

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

```swift
AnimatedImage(name: "animation2") // Just for showcase, don't mix them at the same time
.indicator(SDWebImageProgressIndicator.default) // UIKit indicator component
.indicator(Indicator.progress) // SwiftUI indicator component
.transition(SDWebImageTransition.flipFromLeft) // UIKit animation transition
.transition(AnyTransition.flipFromLeft) // SwiftUI animation transition
var body: some View {
AnimatedImage(name: "animation2") // Just for showcase, don't mix them at the same time
.indicator(SDWebImageProgressIndicator.default) // UIKit indicator component
.indicator(Indicator.progress) // SwiftUI indicator component
.transition(SDWebImageTransition.flipFromLeft) // UIKit animation transition
.transition(AnyTransition.flipFromLeft) // SwiftUI animation transition
}
```

### Which View to choose
Expand Down
35 changes: 24 additions & 11 deletions SDWebImageSwiftUI/Classes/WebImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,7 @@ public struct WebImage : View {
}
}
} else {
Group {
if placeholder != nil {
placeholder
} else {
// Should not use `EmptyView`, which does not respect to the container's frame modifier
// Using a empty image instead for better compatible
configurations.reduce(Image.empty) { (previous, configuration) in
configuration(previous)
}
}
}
setupPlaceholder()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.onAppear {
guard self.retryOnAppear else { return }
Expand All @@ -136,6 +126,29 @@ public struct WebImage : View {
}
}

func configure(image: Image) -> some View {
// Should not use `EmptyView`, which does not respect to the container's frame modifier
// Using a empty image instead for better compatible
configurations.reduce(image) { (previous, configuration) in
configuration(previous)
}
}

/// Placeholder View Support
func setupPlaceholder() -> some View {
// Don't use `Group` because it will trigger `.onAppear` and `.onDisappear` when condition view removed, treat placeholder as an entire component
if let placeholder = placeholder {
// If use `.delayPlaceholder`, the placeholder is applied after loading failed, hide during loading :)
if imageManager.options.contains(.delayPlaceholder) && imageManager.isLoading {
return AnyView(configure(image: Image.empty))
} else {
return placeholder
}
} else {
return AnyView(configure(image: Image.empty))
}
}

/// Animated Image Support
func setupPlayer(image: PlatformImage?) {
if imagePlayer != nil {
Expand Down

0 comments on commit 42fdca6

Please sign in to comment.