Skip to content

Refactory the indicator API on WebImage, using the same design pattern like SwiftUI, return some View #31

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 4 commits into from
Oct 27, 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
12 changes: 6 additions & 6 deletions Example/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -33,8 +33,8 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
libwebp: 057912d6d0abfb6357d8bb05c0ea470301f5d61e
SDWebImage: 46a7f73228f84ce80990c786e4372cf4db5875ce
SDWebImageSwiftUI: b91be76ecb0cdf74c18f6cd92aae8f19a9ded02d
SDWebImage: 4eabf2fa6695c95c47724214417a9c036c965e4a
SDWebImageSwiftUI: 3a3e3aa707aeedf024af6f9c718177f681edf9de
SDWebImageWebPCoder: 947093edd1349d820c40afbd9f42acb6cdecd987

PODFILE CHECKSUM: 3fb06a5173225e197f3a4bf2be7e5586a693257a
Expand Down
2 changes: 1 addition & 1 deletion Example/SDWebImageSwiftUIDemo/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
14 changes: 6 additions & 8 deletions Example/SDWebImageSwiftUIDemo/DetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +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])
.indicator(
Indicator { isAnimating, progress in
ProgressBar(value: progress)
.foregroundColor(.blue)
.frame(maxHeight: 6)
}
)
.resizable()
.indicator { isAnimating, progress in
ProgressBar(value: progress)
.foregroundColor(.blue)
.frame(maxHeight: 6)
}
.scaledToFit()
#endif
}
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion SDWebImageSwiftUI/Classes/ImageViewWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
45 changes: 38 additions & 7 deletions SDWebImageSwiftUI/Classes/Indicator/Indicator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,52 @@ import Foundation
import SwiftUI

/// A type to build the indicator
public struct Indicator {
var builder: (Binding<Bool>, Binding<CGFloat>) -> AnyView
public struct Indicator<T> where T : View {
var builder: (Binding<Bool>, Binding<CGFloat>) -> 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<T>(@ViewBuilder builder: @escaping (_ isAnimating: Binding<Bool>, _ progress: Binding<CGFloat>) -> T) where T : View {
self.builder = { isAnimating, progress in
AnyView(builder(isAnimating, progress))
public init(@ViewBuilder builder: @escaping (_ isAnimating: Binding<Bool>, _ progress: Binding<CGFloat>) -> 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<T, Content> : View where T : View, Content : View {
@ObservedObject var imageManager: ImageManager

var indicator: Indicator<T>
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<T>, 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
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions SDWebImageSwiftUI/Classes/Indicator/ProgressIndicator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
55 changes: 9 additions & 46 deletions SDWebImageSwiftUI/Classes/WebImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
}
}

Expand Down Expand Up @@ -174,10 +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<T>(_ indicator: Indicator<T>) -> 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<T>(@ViewBuilder builder: @escaping (_ isAnimating: Binding<Bool>, _ progress: Binding<CGFloat>) -> T) -> some View where T : View {
return indicator(Indicator(builder: builder))
}
}

Expand Down