From fa6907fe1fc0eff28dd5fb264fb3138e83e545f7 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 27 Oct 2019 14:42:07 +0800 Subject: [PATCH 1/4] Add one convenient method on WebImage to associated indicator with view builder block --- Example/Podfile.lock | 12 ++++++------ Example/SDWebImageSwiftUIDemo/DetailView.swift | 12 +++++------- SDWebImageSwiftUI/Classes/WebImage.swift | 8 ++++++++ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 26923eb4..0989c952 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -8,10 +8,10 @@ PODS: - libwebp/mux (1.0.3): - libwebp/demux - libwebp/webp (1.0.3) - - SDWebImage (5.2.3): - - SDWebImage/Core (= 5.2.3) - - SDWebImage/Core (5.2.3) - - SDWebImageSwiftUI (0.4.2): + - SDWebImage (5.2.5): + - SDWebImage/Core (= 5.2.5) + - SDWebImage/Core (5.2.5) + - SDWebImageSwiftUI (0.5.2): - SDWebImage (~> 5.1) - SDWebImageWebPCoder (0.2.5): - libwebp (~> 1.0) @@ -33,8 +33,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: libwebp: 057912d6d0abfb6357d8bb05c0ea470301f5d61e - SDWebImage: 46a7f73228f84ce80990c786e4372cf4db5875ce - SDWebImageSwiftUI: b91be76ecb0cdf74c18f6cd92aae8f19a9ded02d + SDWebImage: 4eabf2fa6695c95c47724214417a9c036c965e4a + SDWebImageSwiftUI: 3a3e3aa707aeedf024af6f9c718177f681edf9de SDWebImageWebPCoder: 947093edd1349d820c40afbd9f42acb6cdecd987 PODFILE CHECKSUM: 3fb06a5173225e197f3a4bf2be7e5586a693257a diff --git a/Example/SDWebImageSwiftUIDemo/DetailView.swift b/Example/SDWebImageSwiftUIDemo/DetailView.swift index c5e1cf92..e8523e40 100644 --- a/Example/SDWebImageSwiftUIDemo/DetailView.swift +++ b/Example/SDWebImageSwiftUIDemo/DetailView.swift @@ -62,13 +62,11 @@ struct DetailView: View { .scaledToFit() #else WebImage(url: URL(string:url), options: [.progressiveLoad]) - .indicator( - Indicator { isAnimating, progress in - ProgressBar(value: progress) - .foregroundColor(.blue) - .frame(maxHeight: 6) - } - ) + .indicator { isAnimating, progress in + ProgressBar(value: progress) + .foregroundColor(.blue) + .frame(maxHeight: 6) + } .resizable() .scaledToFit() #endif diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index e087b418..b6326777 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -179,6 +179,14 @@ extension WebImage { result.indicator = indicator return result } + + /// Associate a indicator when loading image with url, convenient method with block + /// - Parameter indicator: The indicator type, see `Indicator` + public func indicator(@ViewBuilder builder: @escaping (_ isAnimating: Binding, _ progress: Binding) -> T) -> WebImage where T : View { + var result = self + result.indicator = Indicator(builder: builder) + return result + } } #if DEBUG From ab6d78a407508389a6e92eef0592bad96d5c455e Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 27 Oct 2019 16:16:29 +0800 Subject: [PATCH 2/4] Using the SwiftUI view modifer design pattern, to re-create indicator API. Now it will rerturn some View instead of WebImage --- .../SDWebImageSwiftUIDemo/ContentView.swift | 2 +- .../SDWebImageSwiftUIDemo/DetailView.swift | 4 +- .../Classes/Indicator/Indicator.swift | 45 ++++++++++++--- SDWebImageSwiftUI/Classes/WebImage.swift | 55 ++----------------- 4 files changed, 46 insertions(+), 60 deletions(-) diff --git a/Example/SDWebImageSwiftUIDemo/ContentView.swift b/Example/SDWebImageSwiftUIDemo/ContentView.swift index 4c2287c1..38d1b04c 100644 --- a/Example/SDWebImageSwiftUIDemo/ContentView.swift +++ b/Example/SDWebImageSwiftUIDemo/ContentView.swift @@ -96,8 +96,8 @@ struct ContentView: View { } else { #if os(macOS) || os(iOS) || os(tvOS) WebImage(url: URL(string:url)) - .indicator(.activity) .resizable() + .indicator(.activity) .scaledToFit() .frame(width: CGFloat(100), height: CGFloat(100), alignment: .center) .animation(.easeInOut(duration: 0.5)) diff --git a/Example/SDWebImageSwiftUIDemo/DetailView.swift b/Example/SDWebImageSwiftUIDemo/DetailView.swift index e8523e40..51d79be9 100644 --- a/Example/SDWebImageSwiftUIDemo/DetailView.swift +++ b/Example/SDWebImageSwiftUIDemo/DetailView.swift @@ -57,17 +57,17 @@ struct DetailView: View { } else { #if os(macOS) || os(iOS) || os(tvOS) WebImage(url: URL(string:url), options: [.progressiveLoad]) - .indicator(.progress) .resizable() + .indicator(.progress) .scaledToFit() #else WebImage(url: URL(string:url), options: [.progressiveLoad]) + .resizable() .indicator { isAnimating, progress in ProgressBar(value: progress) .foregroundColor(.blue) .frame(maxHeight: 6) } - .resizable() .scaledToFit() #endif } diff --git a/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift b/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift index b434ea52..c0ebede1 100644 --- a/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift +++ b/SDWebImageSwiftUI/Classes/Indicator/Indicator.swift @@ -10,23 +10,52 @@ import Foundation import SwiftUI /// A type to build the indicator -public struct Indicator { - var builder: (Binding, Binding) -> AnyView +public struct Indicator where T : View { + var builder: (Binding, Binding) -> T /// Create a indicator with builder /// - Parameter builder: A builder to build indicator /// - Parameter isAnimating: A Binding to control the animation. If image is during loading, the value is true, else (like start loading) the value is false. /// - Parameter progress: A Binding to control the progress during loading. If no progress can be reported, the value is 0. /// Associate a indicator when loading image with url - public init(@ViewBuilder builder: @escaping (_ isAnimating: Binding, _ progress: Binding) -> T) where T : View { - self.builder = { isAnimating, progress in - AnyView(builder(isAnimating, progress)) + public init(@ViewBuilder builder: @escaping (_ isAnimating: Binding, _ progress: Binding) -> T) { + self.builder = builder + } +} + +/// A implementation detail View with indicator +/// SwiftUI View Modifier construced by using a internal View type which modify the `body` +/// It use type system to represent the view hierarchy, and Swift `some View` syntax to hide the type detail for users +struct IndicatorView : View where T : View, Content : View { + @ObservedObject var imageManager: ImageManager + + var indicator: Indicator + var content: Content + + var body: some View { + if (imageManager.image != nil) && !imageManager.isLoading { + // Disable Indiactor + return AnyView(content) + } else { + // Enable indicator + return AnyView( + ZStack { + content + indicator.builder($imageManager.isLoading, $imageManager.progress) + } + ) } } + + public init(_ view: Content, indicator: Indicator, imageManager: ImageManager) { + self.content = view + self.indicator = indicator + self.imageManager = imageManager + } } #if os(macOS) || os(iOS) || os(tvOS) -extension Indicator { +extension Indicator where T == ActivityIndicator { /// Activity Indicator public static var activity: Indicator { Indicator { isAnimating, _ in @@ -41,7 +70,9 @@ extension Indicator { ActivityIndicator(isAnimating, style: style) } } - +} + +extension Indicator where T == ProgressIndicator { /// Progress Indicator public static var progress: Indicator { Indicator { isAnimating, progress in diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index b6326777..46a59b89 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -16,14 +16,8 @@ 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 - var isFinished: Bool { - !isLoading && (imageManager.image != nil) - } /// Create a web image with url, placeholder, custom options and context. /// - Parameter url: The image url @@ -52,7 +46,7 @@ public struct WebImage : View { // this can ensure we load the image, SDWebImage take care of the duplicated query self.imageManager.load() } - let view = configurations.reduce(image) { (previous, configuration) in + return configurations.reduce(image) { (previous, configuration) in configuration(previous) } .onAppear { @@ -63,41 +57,6 @@ public struct WebImage : View { .onDisappear { self.imageManager.cancel() } - // Convert Combine.Publisher to Binding - .onReceive(imageManager.$isLoading) { isLoading in - // only Apple Watch complain that "Modifying state during view update, this will cause undefined behavior." - // Use dispatch to workaround, Thanks Apple :) - #if os(watchOS) - DispatchQueue.main.async { - self.isLoading = isLoading - } - #else - self.isLoading = isLoading - #endif - } - .onReceive(imageManager.$progress) { progress in - #if os(watchOS) - DispatchQueue.main.async { - self.progress = progress - } - #else - self.progress = progress - #endif - } - if let indicator = indicator { - if isFinished { - return AnyView(view) - } else { - return AnyView( - ZStack { - view - indicator.builder($isLoading, $progress) - } - ) - } - } else { - return AnyView(view) - } } } @@ -174,18 +133,14 @@ extension WebImage { /// Associate a indicator when loading image with url /// - Parameter indicator: The indicator type, see `Indicator` - public func indicator(_ indicator: Indicator?) -> WebImage { - var result = self - result.indicator = indicator - return result + public func indicator(_ indicator: Indicator) -> some View where T : View { + return IndicatorView(self, indicator: indicator, imageManager: imageManager) } /// Associate a indicator when loading image with url, convenient method with block /// - Parameter indicator: The indicator type, see `Indicator` - public func indicator(@ViewBuilder builder: @escaping (_ isAnimating: Binding, _ progress: Binding) -> T) -> WebImage where T : View { - var result = self - result.indicator = Indicator(builder: builder) - return result + public func indicator(@ViewBuilder builder: @escaping (_ isAnimating: Binding, _ progress: Binding) -> T) -> some View where T : View { + return indicator(Indicator(builder: builder)) } } From 697dc8b2bb03259d050442a82022080f7433d26c Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 27 Oct 2019 16:36:22 +0800 Subject: [PATCH 3/4] Update the readme with indicator code --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 847401b1..41c11215 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,8 @@ var body: some View { .onSuccess { image, cacheType in // Success } - .indicator(.activity) // Activity Indicator .resizable() + .indicator(.activity) // Activity Indicator .scaledToFit() .frame(width: 300, height: 300, alignment: .center) } From 25304b6663e3f35acc83c3db83cf6f9531e88eca Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 27 Oct 2019 17:01:49 +0800 Subject: [PATCH 4/4] Fix the progerss indicator width on macOS. After we use the new solution for indicator --- SDWebImageSwiftUI/Classes/ImageViewWrapper.swift | 2 +- SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift index 1aa5f779..469519c6 100644 --- a/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift +++ b/SDWebImageSwiftUI/Classes/ImageViewWrapper.swift @@ -65,7 +65,7 @@ public class ProgressIndicatorWrapper : PlatformView { #if os(macOS) public override func layout() { super.layout() - wrapped.setFrameOrigin(CGPoint(x: (self.bounds.width - wrapped.frame.width) / 2, y: (self.bounds.height - wrapped.frame.height) / 2)) + wrapped.setFrameOrigin(CGPoint(x: round(self.bounds.width - wrapped.frame.width) / 2, y: round(self.bounds.height - wrapped.frame.height) / 2)) } #else public override func layoutSubviews() { diff --git a/SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift b/SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift index 9ac613c2..2cd26973 100644 --- a/SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift +++ b/SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift @@ -67,6 +67,9 @@ public struct ProgressIndicator: PlatformViewRepresentable { view.style = .bar view.isDisplayedWhenStopped = false view.controlSize = .small + view.frame = CGRect(x: 0, y: 0, width: 160, height: 0) // Width from `UIProgressView` default width + view.sizeToFit() + view.autoresizingMask = [.maxXMargin, .minXMargin, .maxYMargin, .minYMargin] return nsView }