From 370400230f640c9ef6516997ca54702d997857e4 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 3 Oct 2019 16:33:39 +0800 Subject: [PATCH 1/2] Add the onSuccess, onFailure, onProgress methods to AnimatedImage/WebImage, make it possible to notify result --- SDWebImageSwiftUI/Classes/AnimatedImage.swift | 40 ++++++++++++++----- SDWebImageSwiftUI/Classes/ImageManager.swift | 13 +++++- SDWebImageSwiftUI/Classes/WebImage.swift | 16 ++++++++ 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/SDWebImageSwiftUI/Classes/AnimatedImage.swift b/SDWebImageSwiftUI/Classes/AnimatedImage.swift index 9ea2f0c..cfecb1b 100644 --- a/SDWebImageSwiftUI/Classes/AnimatedImage.swift +++ b/SDWebImageSwiftUI/Classes/AnimatedImage.swift @@ -15,6 +15,9 @@ import SDWebImage final class AnimatedImageModel : ObservableObject { @Published var image: PlatformImage? @Published var url: URL? + @Published var successBlock: ((PlatformImage, SDImageCacheType) -> Void)? + @Published var failureBlock: ((Error) -> Void)? + @Published var progressBlock: ((Int, Int) -> Void)? } // Layout Binding Object @@ -67,7 +70,15 @@ public struct AnimatedImage : ViewRepresentable { func updateView(_ view: AnimatedImageViewWrapper, context: ViewRepresentableContext) { view.wrapped.image = imageModel.image if let url = imageModel.url { - view.wrapped.sd_setImage(with: url, placeholderImage: nil, options: webOptions, context: webContext) + view.wrapped.sd_setImage(with: url, placeholderImage: nil, options: webOptions, context: webContext, progress: { (receivedSize, expectedSize, _) in + self.imageModel.progressBlock?(receivedSize, expectedSize) + }) { (image, error, cacheType, _) in + if let image = image { + self.imageModel.successBlock?(image, cacheType) + } else { + self.imageModel.failureBlock?(error ?? NSError()) + } + } } layoutView(view, context: context) @@ -179,16 +190,6 @@ public struct AnimatedImage : ViewRepresentable { #endif } - public func image(_ image: PlatformImage?) -> Self { - imageModel.image = image - return self - } - - public func imageUrl(_ url: URL?) -> Self { - imageModel.url = url - return self - } - public func resizable( capInsets: EdgeInsets = EdgeInsets(), resizingMode: Image.ResizingMode = .stretch) -> AnimatedImage @@ -236,6 +237,23 @@ public struct AnimatedImage : ViewRepresentable { } } +extension AnimatedImage { + public func onFailure(perform action: ((Error) -> Void)? = nil) -> AnimatedImage { + imageModel.failureBlock = action + return self + } + + public func onSuccess(perform action: ((PlatformImage, SDImageCacheType) -> Void)? = nil) -> AnimatedImage { + imageModel.successBlock = action + return self + } + + public func onProgress(perform action: ((Int, Int) -> Void)? = nil) -> AnimatedImage { + imageModel.progressBlock = action + return self + } +} + extension AnimatedImage { public init(url: URL?, placeholder: PlatformImage? = nil, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil) { self.webOptions = options diff --git a/SDWebImageSwiftUI/Classes/ImageManager.swift b/SDWebImageSwiftUI/Classes/ImageManager.swift index d9ac3b1..1911a1e 100644 --- a/SDWebImageSwiftUI/Classes/ImageManager.swift +++ b/SDWebImageSwiftUI/Classes/ImageManager.swift @@ -26,6 +26,9 @@ class ImageManager : ObservableObject { var url: URL? var options: SDWebImageOptions var context: [SDWebImageContextOption : Any]? + var successBlock: ((PlatformImage, SDImageCacheType) -> Void)? + var failureBlock: ((Error) -> Void)? + var progressBlock: ((Int, Int) -> Void)? init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil) { self.url = url @@ -34,9 +37,17 @@ class ImageManager : ObservableObject { } func load() { - currentOperation = manager.loadImage(with: url, options: options, context: context, progress: nil) { (image, data, error, cacheType, _, _) in + currentOperation = manager.loadImage(with: url, options: options, context: context, progress: { [weak self] (receivedSize, expectedSize, _) in + self?.progressBlock?(receivedSize, expectedSize) + }) { [weak self] (image, data, error, cacheType, _, _) in + guard let self = self else { + return + } if let image = image { self.image = image + self.successBlock?(image, cacheType) + } else { + self.failureBlock?(error ?? NSError()) } } } diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index 793c60b..4b68495 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -82,6 +82,22 @@ extension WebImage { } } +extension WebImage { + public func onFailure(perform action: ((Error) -> Void)? = nil) -> WebImage { + self.imageManager.failureBlock = action + return self + } + + public func onSuccess(perform action: ((PlatformImage, SDImageCacheType) -> Void)? = nil) -> WebImage { + self.imageManager.successBlock = action + return self + } + + public func onProgress(perform action: ((Int, Int) -> Void)? = nil) -> WebImage { + self.imageManager.progressBlock = action + return self + } +} #if DEBUG struct WebImage_Previews : PreviewProvider { From f5b6d1e549f5e2d0eacfb92da0d50d1644bdb410 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 3 Oct 2019 18:01:31 +0800 Subject: [PATCH 2/2] Update the demo with onProgress with the progressBar --- .../project.pbxproj | 4 ++ .../SDWebImageSwiftUIDemo/DetailView.swift | 43 +++++++++++++++---- .../SDWebImageSwiftUIDemo/ProgressBar.swift | 28 ++++++++++++ SDWebImageSwiftUI/Classes/AnimatedImage.swift | 7 ++- SDWebImageSwiftUI/Classes/WebImage.swift | 2 + 5 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 Example/SDWebImageSwiftUIDemo/ProgressBar.swift diff --git a/Example/SDWebImageSwiftUI.xcodeproj/project.pbxproj b/Example/SDWebImageSwiftUI.xcodeproj/project.pbxproj index c9c7147..6f99376 100644 --- a/Example/SDWebImageSwiftUI.xcodeproj/project.pbxproj +++ b/Example/SDWebImageSwiftUI.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 320CDC3222FADB45007CF858 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 320CDC3122FADB45007CF858 /* Assets.xcassets */; }; 320CDC3522FADB45007CF858 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 320CDC3422FADB45007CF858 /* Preview Assets.xcassets */; }; 320CDC3822FADB45007CF858 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 320CDC3622FADB45007CF858 /* LaunchScreen.storyboard */; }; + 321A6BF02345EC4E00B5BEFC /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 321A6BEF2345EC4E00B5BEFC /* ProgressBar.swift */; }; 326B0D712345C01900D28269 /* DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B0D702345C01900D28269 /* DetailView.swift */; }; CECA1658ECBAF54E3FF3EF58 /* Pods_SDWebImageSwiftUIDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A371F81C3B5BD6972F7A0E2 /* Pods_SDWebImageSwiftUIDemo.framework */; }; /* End PBXBuildFile section */ @@ -28,6 +29,7 @@ 320CDC3422FADB45007CF858 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 320CDC3722FADB45007CF858 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 320CDC3922FADB45007CF858 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 321A6BEF2345EC4E00B5BEFC /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; 326B0D702345C01900D28269 /* DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailView.swift; sourceTree = ""; }; 3E9F8B5F06960FFFBD1A5F99 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 4A371F81C3B5BD6972F7A0E2 /* Pods_SDWebImageSwiftUIDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SDWebImageSwiftUIDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -70,6 +72,7 @@ 320CDC2D22FADB44007CF858 /* SceneDelegate.swift */, 320CDC2F22FADB44007CF858 /* ContentView.swift */, 326B0D702345C01900D28269 /* DetailView.swift */, + 321A6BEF2345EC4E00B5BEFC /* ProgressBar.swift */, 320CDC3122FADB45007CF858 /* Assets.xcassets */, 320CDC3622FADB45007CF858 /* LaunchScreen.storyboard */, 320CDC3922FADB45007CF858 /* Info.plist */, @@ -268,6 +271,7 @@ 320CDC2C22FADB44007CF858 /* AppDelegate.swift in Sources */, 326B0D712345C01900D28269 /* DetailView.swift in Sources */, 320CDC2E22FADB44007CF858 /* SceneDelegate.swift in Sources */, + 321A6BF02345EC4E00B5BEFC /* ProgressBar.swift in Sources */, 320CDC3022FADB44007CF858 /* ContentView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Example/SDWebImageSwiftUIDemo/DetailView.swift b/Example/SDWebImageSwiftUIDemo/DetailView.swift index 3b41f8a..3455fb3 100644 --- a/Example/SDWebImageSwiftUIDemo/DetailView.swift +++ b/Example/SDWebImageSwiftUIDemo/DetailView.swift @@ -12,18 +12,43 @@ import SDWebImageSwiftUI struct DetailView: View { let url: String let animated: Bool + @State var progress: CGFloat = 1 var body: some View { - Group { - if animated { - AnimatedImage(url: URL(string:url), options: [.progressiveLoad]) - .resizable() - .scaledToFit() - } else { - WebImage(url: URL(string:url), options: [.progressiveLoad]) - .resizable() - .scaledToFit() + VStack { + HStack { + ProgressBar(value: $progress) + .foregroundColor(.blue) + .frame(maxHeight: 6) } + Spacer() + HStack { + if animated { + AnimatedImage(url: URL(string:url), options: [.progressiveLoad]) + .onProgress(perform: { (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() + } else { + WebImage(url: URL(string:url), options: [.progressiveLoad]) + .onProgress(perform: { (receivedSize, expectedSize) in + if (expectedSize >= 0) { + self.progress = CGFloat(receivedSize) / CGFloat(expectedSize) + } else { + self.progress = 1 + } + }) + .resizable() + .scaledToFit() + } + } + Spacer() } } } diff --git a/Example/SDWebImageSwiftUIDemo/ProgressBar.swift b/Example/SDWebImageSwiftUIDemo/ProgressBar.swift new file mode 100644 index 0000000..44e586e --- /dev/null +++ b/Example/SDWebImageSwiftUIDemo/ProgressBar.swift @@ -0,0 +1,28 @@ +/* +* This file is part of the SDWebImage package. +* (c) DreamPiggy +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +import SwiftUI + +/// A linear view that depicts the progress of a task over time. +public struct ProgressBar: View { + @Binding var value: CGFloat + + public var body: some View { + GeometryReader { geometry in + ZStack(alignment: .topLeading) { + Capsule() + .frame(width: geometry.size.width) + .opacity(0.3) + Rectangle() + .frame(width: geometry.size.width * self.value) + } + } + .clipShape(Capsule()) + .opacity(self.value < 1 ? 1 : 0) + } +} diff --git a/SDWebImageSwiftUI/Classes/AnimatedImage.swift b/SDWebImageSwiftUI/Classes/AnimatedImage.swift index cfecb1b..78a59b2 100644 --- a/SDWebImageSwiftUI/Classes/AnimatedImage.swift +++ b/SDWebImageSwiftUI/Classes/AnimatedImage.swift @@ -189,7 +189,10 @@ public struct AnimatedImage : ViewRepresentable { view.setNeedsDisplay() #endif } - +} + +// Layout +extension AnimatedImage { public func resizable( capInsets: EdgeInsets = EdgeInsets(), resizingMode: Image.ResizingMode = .stretch) -> AnimatedImage @@ -237,6 +240,7 @@ public struct AnimatedImage : ViewRepresentable { } } +// Completion Handler extension AnimatedImage { public func onFailure(perform action: ((Error) -> Void)? = nil) -> AnimatedImage { imageModel.failureBlock = action @@ -254,6 +258,7 @@ extension AnimatedImage { } } +// Initializer extension AnimatedImage { public init(url: URL?, placeholder: PlatformImage? = nil, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil) { self.webOptions = options diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index 4b68495..2d38f5c 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -55,6 +55,7 @@ public struct WebImage : View { } } +// Layout extension WebImage { func configure(_ block: @escaping (Image) -> Image) -> WebImage { var result = self @@ -82,6 +83,7 @@ extension WebImage { } } +// Completion Handler extension WebImage { public func onFailure(perform action: ((Error) -> Void)? = nil) -> WebImage { self.imageManager.failureBlock = action