Skip to content

Feature: Support indicator and transition on WebImage/AnimatedImage #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Oct 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Carthage/Build
# `pod install` in .travis.yml
#
Pods/
Podfile.lock

# SwiftPM
.build
Expand Down
4 changes: 2 additions & 2 deletions Example/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ PODS:
- SDWebImage (5.2.3):
- SDWebImage/Core (= 5.2.3)
- SDWebImage/Core (5.2.3)
- SDWebImageSwiftUI (0.4.1):
- SDWebImageSwiftUI (0.4.2):
- SDWebImage (~> 5.1)
- SDWebImageWebPCoder (0.2.5):
- libwebp (~> 1.0)
Expand All @@ -34,7 +34,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
libwebp: 057912d6d0abfb6357d8bb05c0ea470301f5d61e
SDWebImage: 46a7f73228f84ce80990c786e4372cf4db5875ce
SDWebImageSwiftUI: 15eeed7470ba9cd64fa7e8dddd62e12df58d07f3
SDWebImageSwiftUI: b91be76ecb0cdf74c18f6cd92aae8f19a9ded02d
SDWebImageWebPCoder: 947093edd1349d820c40afbd9f42acb6cdecd987

PODFILE CHECKSUM: 3fb06a5173225e197f3a4bf2be7e5586a693257a
Expand Down
19 changes: 19 additions & 0 deletions Example/SDWebImageSwiftUIDemo/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,34 @@ struct ContentView: View {
NavigationLink(destination: DetailView(url: url, animated: self.animated)) {
HStack {
if self.animated {
#if os(macOS) || os(iOS) || os(tvOS)
AnimatedImage(url: URL(string:url))
.indicator(SDWebImageActivityIndicator.medium)
.transition(.fade)
.resizable()
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
#else
AnimatedImage(url: URL(string:url))
.resizable()
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
#endif
} else {
#if os(macOS) || os(iOS) || os(tvOS)
WebImage(url: URL(string:url))
.indicator(.activity)
.resizable()
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
.animation(.easeInOut(duration: 0.5))
.transition(.opacity)
#else
WebImage(url: URL(string:url))
.resizable()
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
#endif
}
Text((url as NSString).lastPathComponent)
}
Expand Down
42 changes: 20 additions & 22 deletions Example/SDWebImageSwiftUIDemo/DetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,10 @@ import SDWebImageSwiftUI
struct DetailView: View {
let url: String
let animated: Bool
@State var progress: CGFloat = 1
@State var isAnimating: Bool = true

var body: some View {
VStack {
HStack {
ProgressBar(value: $progress)
.foregroundColor(.blue)
.frame(maxHeight: 6)
}
Spacer()
#if os(iOS) || os(tvOS)
if animated {
contentView()
Expand All @@ -45,35 +38,40 @@ struct DetailView: View {
contentView()
}
#endif
Spacer()
}
}

func contentView() -> some View {
HStack {
if animated {
#if os(macOS) || os(iOS) || os(tvOS)
AnimatedImage(url: URL(string:url), options: [.progressiveLoad], isAnimating: $isAnimating)
.indicator(SDWebImageProgressIndicator.default)
.resizable()
.scaledToFit()
#else
AnimatedImage(url: URL(string:url), options: [.progressiveLoad], isAnimating: $isAnimating)
.onProgress { receivedSize, expectedSize in
// SwiftUI engine itself ensure the main queue dispatch
if (expectedSize > 0) {
self.progress = CGFloat(receivedSize) / CGFloat(expectedSize)
} else {
self.progress = 1
}
}
.resizable()
.scaledToFit()
#endif
} else {
#if os(macOS) || os(iOS) || os(tvOS)
WebImage(url: URL(string:url), options: [.progressiveLoad])
.onProgress { receivedSize, expectedSize in
if (expectedSize > 0) {
self.progress = CGFloat(receivedSize) / CGFloat(expectedSize)
} else {
self.progress = 1
.indicator(.progress)
.resizable()
.scaledToFit()
#else
WebImage(url: URL(string:url), options: [.progressiveLoad])
.indicator(
Indicator { isAnimating, progress in
ProgressBar(value: progress)
.foregroundColor(.blue)
.frame(maxHeight: 6)
}
}
)
.resizable()
.scaledToFit()
#endif
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions Example/SDWebImageSwiftUIDemo/ProgressBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ public struct ProgressBar: View {

public var body: some View {
GeometryReader { geometry in
ZStack(alignment: .topLeading) {
Capsule()
ZStack(alignment: .leading) {
Rectangle()
.frame(width: geometry.size.width)
.opacity(0.3)
Rectangle()
.frame(width: geometry.size.width * self.value)
.opacity(0.6)
}
}
.clipShape(Capsule())
.opacity(self.value < 1 ? 1 : 0)
.cornerRadius(2)
}
}
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,19 @@ let package = Package(

### Using `WebImage` to load network image

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

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.
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.

```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
}
.indicator(.activity) // Activity Indicator
.resizable()
.scaledToFit()
.frame(width: 300, height: 300, alignment: .center)
Expand All @@ -88,6 +90,7 @@ var body: some View {
.onFailure { error in
// Error
}
.transition(.fade) // Fade Transition
.scaledToFit()
// Data
AnimatedImage(data: try! Data(contentsOf: URL(fileURLWithPath: "/tmp/foo.webp")))
Expand All @@ -101,9 +104,10 @@ var body: some View {

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

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.
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.

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.

Expand Down
38 changes: 38 additions & 0 deletions SDWebImageSwiftUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@
324F61CC235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */; };
324F61CD235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */; };
324F61CE235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */; };
326B84822363350C0011BDFB /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B84812363350C0011BDFB /* Indicator.swift */; };
326B84832363350C0011BDFB /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B84812363350C0011BDFB /* Indicator.swift */; };
326B84842363350C0011BDFB /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B84812363350C0011BDFB /* Indicator.swift */; };
326B84852363350C0011BDFB /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B84812363350C0011BDFB /* Indicator.swift */; };
326B8487236335110011BDFB /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B8486236335110011BDFB /* ActivityIndicator.swift */; };
326B8488236335110011BDFB /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B8486236335110011BDFB /* ActivityIndicator.swift */; };
326B8489236335110011BDFB /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B8486236335110011BDFB /* ActivityIndicator.swift */; };
326B848A236335110011BDFB /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B8486236335110011BDFB /* ActivityIndicator.swift */; };
326B848C236335400011BDFB /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B848B236335400011BDFB /* ProgressIndicator.swift */; };
326B848D236335400011BDFB /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B848B236335400011BDFB /* ProgressIndicator.swift */; };
326B848E236335400011BDFB /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B848B236335400011BDFB /* ProgressIndicator.swift */; };
326B848F236335400011BDFB /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B848B236335400011BDFB /* ProgressIndicator.swift */; };
326E480A23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326E480923431C0F00C633E9 /* ImageViewWrapper.swift */; };
Expand Down Expand Up @@ -99,6 +111,9 @@
/* Begin PBXFileReference section */
324F61C5235E07EC003973B8 /* SDAnimatedImageInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDAnimatedImageInterface.h; sourceTree = "<group>"; };
324F61C6235E07EC003973B8 /* SDAnimatedImageInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDAnimatedImageInterface.m; sourceTree = "<group>"; };
326B84812363350C0011BDFB /* Indicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Indicator.swift; sourceTree = "<group>"; };
326B8486236335110011BDFB /* ActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = "<group>"; };
326B848B236335400011BDFB /* ProgressIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressIndicator.swift; sourceTree = "<group>"; };
326E480923431C0F00C633E9 /* ImageViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewWrapper.swift; sourceTree = "<group>"; };
32C43DCC22FD540D00BE87F5 /* SDWebImageSwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SDWebImageSwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
32C43DDC22FD54C600BE87F5 /* ImageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -161,6 +176,16 @@
path = ObjC;
sourceTree = "<group>";
};
326099472362E09E006EBB22 /* Indicator */ = {
isa = PBXGroup;
children = (
326B84812363350C0011BDFB /* Indicator.swift */,
326B8486236335110011BDFB /* ActivityIndicator.swift */,
326B848B236335400011BDFB /* ProgressIndicator.swift */,
);
path = Indicator;
sourceTree = "<group>";
};
32C43DC222FD540D00BE87F5 = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -194,6 +219,7 @@
32C43DDB22FD54C600BE87F5 /* Classes */ = {
isa = PBXGroup;
children = (
326099472362E09E006EBB22 /* Indicator */,
324F61C4235E07EC003973B8 /* ObjC */,
32C43DDC22FD54C600BE87F5 /* ImageManager.swift */,
32C43DDE22FD54C600BE87F5 /* WebImage.swift */,
Expand Down Expand Up @@ -418,8 +444,11 @@
buildActionMask = 2147483647;
files = (
32C43E1722FD583700BE87F5 /* WebImage.swift in Sources */,
326B848C236335400011BDFB /* ProgressIndicator.swift in Sources */,
326B84822363350C0011BDFB /* Indicator.swift in Sources */,
32C43E3222FD5DE100BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
326E480A23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
326B8487236335110011BDFB /* ActivityIndicator.swift in Sources */,
32C43E1622FD583700BE87F5 /* ImageManager.swift in Sources */,
32C43E1822FD583700BE87F5 /* AnimatedImage.swift in Sources */,
324F61CB235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */,
Expand All @@ -431,8 +460,11 @@
buildActionMask = 2147483647;
files = (
32C43E1A22FD583700BE87F5 /* WebImage.swift in Sources */,
326B848D236335400011BDFB /* ProgressIndicator.swift in Sources */,
326B84832363350C0011BDFB /* Indicator.swift in Sources */,
32C43E3322FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
326E480B23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
326B8488236335110011BDFB /* ActivityIndicator.swift in Sources */,
32C43E1922FD583700BE87F5 /* ImageManager.swift in Sources */,
32C43E1B22FD583700BE87F5 /* AnimatedImage.swift in Sources */,
324F61CC235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */,
Expand All @@ -444,8 +476,11 @@
buildActionMask = 2147483647;
files = (
32C43E1D22FD583800BE87F5 /* WebImage.swift in Sources */,
326B848E236335400011BDFB /* ProgressIndicator.swift in Sources */,
326B84842363350C0011BDFB /* Indicator.swift in Sources */,
32C43E3422FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
326E480C23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
326B8489236335110011BDFB /* ActivityIndicator.swift in Sources */,
32C43E1C22FD583800BE87F5 /* ImageManager.swift in Sources */,
32C43E1E22FD583800BE87F5 /* AnimatedImage.swift in Sources */,
324F61CD235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */,
Expand All @@ -457,8 +492,11 @@
buildActionMask = 2147483647;
files = (
32C43E2022FD583800BE87F5 /* WebImage.swift in Sources */,
326B848F236335400011BDFB /* ProgressIndicator.swift in Sources */,
326B84852363350C0011BDFB /* Indicator.swift in Sources */,
32C43E3522FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */,
326E480D23431C0F00C633E9 /* ImageViewWrapper.swift in Sources */,
326B848A236335110011BDFB /* ActivityIndicator.swift in Sources */,
32C43E1F22FD583800BE87F5 /* ImageManager.swift in Sources */,
32C43E2122FD583800BE87F5 /* AnimatedImage.swift in Sources */,
324F61CE235E07EC003973B8 /* SDAnimatedImageInterface.m in Sources */,
Expand Down
31 changes: 31 additions & 0 deletions SDWebImageSwiftUI/Classes/AnimatedImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ final class AnimatedImageConfiguration: ObservableObject {
@Published var incrementalLoad: Bool?
@Published var maxBufferSize: UInt?
@Published var customLoopCount: Int?
#if os(macOS) || os(iOS) || os(tvOS)
// These configurations only useful for web image loading
@Published var indicator: SDWebImageIndicator?
@Published var transition: SDWebImageTransition?
#endif
}

// Convenient
Expand Down Expand Up @@ -203,6 +208,10 @@ public struct AnimatedImage : PlatformViewRepresentable {
#endif
} else {
if let url = url {
#if os(macOS) || os(iOS) || os(tvOS)
view.wrapped.sd_imageIndicator = imageConfiguration.indicator
view.wrapped.sd_imageTransition = imageConfiguration.transition
#endif
loadImage(view, url: url)
}
}
Expand Down Expand Up @@ -545,6 +554,28 @@ extension AnimatedImage {
}
}

#if os(macOS) || os(iOS) || os(tvOS)
// Web Image convenience
extension AnimatedImage {

/// Associate a indicator when loading image with url
/// - Note: If you do not need indicator, specify nil. Defaults to nil
/// - Parameter indicator: indicator, see more in `SDWebImageIndicator`
public func indicator(_ indicator: SDWebImageIndicator?) -> AnimatedImage {
imageConfiguration.indicator = indicator
return self
}

/// Associate a transition when loading image with url
/// - Note: If you specify nil, do not do transition. Defautls to nil.
/// - Parameter transition: transition, see more in `SDWebImageTransition`
public func transition(_ transition: SDWebImageTransition?) -> AnimatedImage {
imageConfiguration.transition = transition
return self
}
}
#endif

#if DEBUG
struct AnimatedImage_Previews : PreviewProvider {
static var previews: some View {
Expand Down
Loading