Add support for WebImage to use indicator. Using protocol and struct based solution, and with SwiftUI Binding for isAnimating and progress

This commit is contained in:
DreamPiggy 2019-10-25 21:48:03 +08:00
parent cbe282b69c
commit 6a2eb0264a
7 changed files with 155 additions and 4 deletions

View File

@ -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)
@ -34,7 +34,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
libwebp: 057912d6d0abfb6357d8bb05c0ea470301f5d61e
SDWebImage: 46a7f73228f84ce80990c786e4372cf4db5875ce
SDWebImageSwiftUI: 15eeed7470ba9cd64fa7e8dddd62e12df58d07f3
SDWebImageSwiftUI: b91be76ecb0cdf74c18f6cd92aae8f19a9ded02d
SDWebImageWebPCoder: 947093edd1349d820c40afbd9f42acb6cdecd987
PODFILE CHECKSUM: 3fb06a5173225e197f3a4bf2be7e5586a693257a

View File

@ -81,11 +81,16 @@ struct ContentView: View {
HStack {
if self.animated {
AnimatedImage(url: URL(string:url))
.indicator(SDWebImageActivityIndicator.medium)
.transition(.fade)
.resizable()
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
} else {
WebImage(url: URL(string:url))
.indicator { isAnimating, _ in
ActivityIndicator(isAnimating)
}
.resizable()
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)

View File

@ -15,6 +15,14 @@
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 */; };
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 */; };
@ -99,6 +107,8 @@
/* 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>"; };
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>"; };
@ -161,6 +171,15 @@
path = ObjC;
sourceTree = "<group>";
};
326099472362E09E006EBB22 /* Indicator */ = {
isa = PBXGroup;
children = (
326B84812363350C0011BDFB /* Indicator.swift */,
326B8486236335110011BDFB /* ActivityIndicator.swift */,
);
path = Indicator;
sourceTree = "<group>";
};
32C43DC222FD540D00BE87F5 = {
isa = PBXGroup;
children = (
@ -194,6 +213,7 @@
32C43DDB22FD54C600BE87F5 /* Classes */ = {
isa = PBXGroup;
children = (
326099472362E09E006EBB22 /* Indicator */,
324F61C4235E07EC003973B8 /* ObjC */,
32C43DDC22FD54C600BE87F5 /* ImageManager.swift */,
32C43DDE22FD54C600BE87F5 /* WebImage.swift */,
@ -418,8 +438,10 @@
buildActionMask = 2147483647;
files = (
32C43E1722FD583700BE87F5 /* WebImage.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 */,
@ -431,8 +453,10 @@
buildActionMask = 2147483647;
files = (
32C43E1A22FD583700BE87F5 /* WebImage.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 */,
@ -444,8 +468,10 @@
buildActionMask = 2147483647;
files = (
32C43E1D22FD583800BE87F5 /* WebImage.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 */,
@ -457,8 +483,10 @@
buildActionMask = 2147483647;
files = (
32C43E2022FD583800BE87F5 /* WebImage.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 */,

View File

@ -11,6 +11,8 @@ import SDWebImage
class ImageManager : ObservableObject {
@Published var image: PlatformImage?
@Published var isLoading: Bool = false
@Published var progress: CGFloat = 0
var manager = SDWebImageManager.shared
weak var currentOperation: SDWebImageOperation? = nil
@ -32,8 +34,21 @@ class ImageManager : ObservableObject {
if currentOperation != nil {
return
}
self.isLoading = true
currentOperation = manager.loadImage(with: url, options: options, context: context, progress: { [weak self] (receivedSize, expectedSize, _) in
self?.progressBlock?(receivedSize, expectedSize)
guard let self = self else {
return
}
self.progressBlock?(receivedSize, expectedSize)
let progress: CGFloat
if (expectedSize > 0) {
progress = CGFloat(receivedSize) / CGFloat(expectedSize)
} else {
progress = 0
}
DispatchQueue.main.async {
self.progress = progress
}
}) { [weak self] (image, data, error, cacheType, finished, _) in
guard let self = self else {
return
@ -42,6 +57,7 @@ class ImageManager : ObservableObject {
self.image = image
}
if finished {
self.isLoading = false
if let image = image {
self.successBlock?(image, cacheType)
} else {

View File

@ -0,0 +1,50 @@
/*
* This file is part of the SDWebImage package.
* (c) DreamPiggy <lizhuoli1126@126.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import Swift
import SwiftUI
public struct ActivityIndicator: PlatformViewRepresentable {
@Binding var isAnimating: Bool
public init(_ isAnimating: Binding<Bool> = .constant(true)) {
self._isAnimating = isAnimating
}
#if os(macOS)
public typealias NSViewType = NSProgressIndicator
#elseif os(iOS) || os(tvOS)
public typealias UIViewType = UIActivityIndicatorView
#endif
#if os(iOS) || os(tvOS)
public func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
let indicator = UIActivityIndicatorView(style: .medium)
indicator.hidesWhenStopped = true
return indicator
}
public func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
}
#endif
#if os(macOS)
public func makeNSView(context: NSViewRepresentableContext<ActivityIndicator>) -> NSProgressIndicator {
let indicator = NSProgressIndicator()
indicator.style = .spinning
indicator.isDisplayedWhenStopped = false
return indicator
}
public func updateNSView(_ nsView: NSProgressIndicator, context: NSViewRepresentableContext<ActivityIndicator>) {
isAnimating ? nsView.startAnimation(nil) : nsView.stopAnimation(nil)
}
#endif
}

View File

@ -0,0 +1,23 @@
/*
* This file is part of the SDWebImage package.
* (c) DreamPiggy <lizhuoli1126@126.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import Foundation
import SwiftUI
public struct Indicator : View {
var builder: (Binding<Bool>, Binding<CGFloat>) -> AnyView
public typealias Body = Never
public var body: Never {
fatalError()
}
public init<T>(@ViewBuilder builder: @escaping (_ isAnimating: Binding<Bool>, _ progress: Binding<CGFloat>) -> T) where T : View {
self.builder = { isAnimating, progress in
AnyView(builder(isAnimating, progress))
}
}
}

View File

@ -16,8 +16,11 @@ public struct WebImage : View {
var context: [SDWebImageContextOption : Any]?
var configurations: [(Image) -> Image] = []
var indicator: Indicator?
@ObservedObject var imageManager: ImageManager
@State var progress: CGFloat = 0
@State var isLoading: Bool = false
/// Create a web image with url, placeholder, custom options and context.
/// - Parameter url: The image url
@ -46,7 +49,7 @@ public struct WebImage : View {
// this can ensure we load the image, SDWebImage take care of the duplicated query
self.imageManager.load()
}
return configurations.reduce(image) { (previous, configuration) in
let view = configurations.reduce(image) { (previous, configuration) in
configuration(previous)
}
.onAppear {
@ -57,6 +60,19 @@ public struct WebImage : View {
.onDisappear {
self.imageManager.cancel()
}
// Convert Combine.Publisher to Binding, I think this need a better API from Apple :)
.onReceive(imageManager.$isLoading) { self.isLoading = $0 }
.onReceive(imageManager.$progress) { self.progress = $0 }
if let indicator = indicator {
return AnyView(
ZStack {
view
indicator.builder($isLoading, $progress)
}
)
} else {
return AnyView(view)
}
}
}
@ -128,6 +144,19 @@ extension WebImage {
}
}
extension WebImage {
/// Associate a indicator when loading image with url
/// - Parameter builder: builder description
/// - Parameter isAnimating: A Binding to control the animation. If image is loading, the value is true, else false.
/// - Parameter progress: A Binding to control the progress during loading. If no progress can be reported, the value is 0.
public func indicator<T>(_ builder: @escaping (_ isAnimating: Binding<Bool>, _ progress: Binding<CGFloat>) -> T) -> WebImage where T : View {
var result = self
result.indicator = Indicator(builder: builder)
return result
}
}
#if DEBUG
struct WebImage_Previews : PreviewProvider {
static var previews: some View {