From 33c11b1cf08edb95cab5bc5877fd562eb35b275c Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sat, 30 Nov 2019 01:37:28 +0800 Subject: [PATCH] Refactory the pure SwiftUI AnimatedImage implementation, right in the WebImage view. Support watchOS as well :) Good SwiftUI --- .../SDWebImageSwiftUIDemo/ContentView.swift | 12 ++- .../SDWebImageSwiftUIDemo/DetailView.swift | 8 +- SDWebImageSwiftUI/Classes/WebImage.swift | 82 +++++++++++++++++-- SDWebImageSwiftUI/Module/SDWebImageSwiftUI.h | 1 + 4 files changed, 91 insertions(+), 12 deletions(-) diff --git a/Example/SDWebImageSwiftUIDemo/ContentView.swift b/Example/SDWebImageSwiftUIDemo/ContentView.swift index 8559504..39b09ac 100644 --- a/Example/SDWebImageSwiftUIDemo/ContentView.swift +++ b/Example/SDWebImageSwiftUIDemo/ContentView.swift @@ -38,7 +38,7 @@ struct ContentView: View { "https://raw.githubusercontent.com/icons8/flat-color-icons/master/pdf/stack_of_photos.pdf", "https://raw.githubusercontent.com/icons8/flat-color-icons/master/pdf/smartphone_tablet.pdf" ] - @State var animated: Bool = false // You can change between WebImage/AnimatedImage + @State var animated: Bool = true // You can change between WebImage/AnimatedImage var body: some View { #if os(iOS) || os(tvOS) @@ -105,8 +105,16 @@ struct ContentView: View { .scaledToFit() .frame(width: CGFloat(100), height: CGFloat(100), alignment: .center) #else - AnimatedImage(url: URL(string:url)) + WebImage(url: URL(string:url)) .resizable() + .animated() + .indicator { _, _ in + ActivityBar() + .foregroundColor(Color.white) + .frame(width: 50, height: 50) + } + .animation(.easeInOut(duration: 0.5)) + .transition(.fade) .scaledToFit() .frame(width: CGFloat(100), height: CGFloat(100), alignment: .center) #endif diff --git a/Example/SDWebImageSwiftUIDemo/DetailView.swift b/Example/SDWebImageSwiftUIDemo/DetailView.swift index 3c0b03b..7550ef9 100644 --- a/Example/SDWebImageSwiftUIDemo/DetailView.swift +++ b/Example/SDWebImageSwiftUIDemo/DetailView.swift @@ -95,8 +95,14 @@ struct DetailView: View { .resizable() .scaledToFit() #else - AnimatedImage(url: URL(string:url), options: [.progressiveLoad], isAnimating: $isAnimating) + WebImage(url: URL(string:url), options: [.progressiveLoad]) .resizable() + .animated(isAnimating) + .indicator { isAnimating, progress in + ProgressBar(value: progress) + .foregroundColor(.blue) + .frame(maxHeight: 6) + } .scaledToFit() #endif } else { diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index 9e4e937..9556301 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -11,10 +11,6 @@ import SDWebImage /// A Image View type to load image from url. Supports static image format. public struct WebImage : View { - var url: URL? - var options: SDWebImageOptions - var context: [SDWebImageContextOption : Any]? - var configurations: [(Image) -> Image] = [] var placeholder: AnyView? @@ -23,14 +19,16 @@ public struct WebImage : View { @ObservedObject var imageManager: ImageManager + // Animated Image support (Beta) + var animated: Bool = false + @State var currentFrame: PlatformImage? = nil + @State var imagePlayer: SDAnimatedImagePlayer? = nil + /// Create a web image with url, placeholder, custom options and context. /// - Parameter url: The image url /// - Parameter options: The options to use when downloading the image. See `SDWebImageOptions` for the possible values. /// - Parameter context: A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. public init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil) { - self.url = url - self.options = options - self.context = context self.imageManager = ImageManager(url: url, options: options, context: context) // load remote image here, SwiftUI sometimes will create a new View struct without calling `onAppear` (like enter EditMode) :) // this can ensure we load the image, SDWebImage take care of the duplicated query @@ -40,8 +38,29 @@ public struct WebImage : View { public var body: some View { Group { if imageManager.image != nil { - configurations.reduce(Image(platformImage: imageManager.image!)) { (previous, configuration) in - configuration(previous) + if animated { + if currentFrame != nil { + configurations.reduce(Image(platformImage: currentFrame!)) { (previous, configuration) in + configuration(previous) + } + .onAppear { + self.imagePlayer?.startPlaying() + } + .onDisappear { + self.imagePlayer?.pausePlaying() + } + } else { + configurations.reduce(Image(platformImage: imageManager.image!)) { (previous, configuration) in + configuration(previous) + } + .onReceive(imageManager.$image) { image in + self.setupPlayer(image: image) + } + } + } else { + configurations.reduce(Image(platformImage: imageManager.image!)) { (previous, configuration) in + configuration(previous) + } } } else { Group { @@ -194,6 +213,51 @@ extension WebImage { } } +// Animated Image support (Beta) +extension WebImage { + + /// Make the image to support animated images. The animation will start when view appears, and pause when disappears. + /// - Note: Currently we do not have advanced control like binding, reset frame index, playback rate, etc. For those use case, it's recommend to use `AnimatedImage` type instead. (support iOS/tvOS/macOS) + /// - Warning: This API need polishing. In the future we may choose to create a new View type instead. + /// + /// - Parameter animated: Whether or not to enable animationn. + public func animated(_ animated: Bool = true) -> WebImage { + var result = self + result.animated = animated + if animated { + // Update Image Manager + result.imageManager.cancel() + var context = result.imageManager.context ?? [:] + context[.animatedImageClass] = SDAnimatedImage.self + result.imageManager.context = context + result.imageManager.load() + } else { + // Update Image Manager + result.imageManager.cancel() + var context = result.imageManager.context ?? [:] + context[.animatedImageClass] = nil + result.imageManager.context = context + result.imageManager.load() + } + return result + } + + func setupPlayer(image: PlatformImage?) { + if imagePlayer != nil { + return + } + if let animatedImage = image as? SDAnimatedImageProvider { + if let imagePlayer = SDAnimatedImagePlayer(provider: animatedImage) { + imagePlayer.animationFrameHandler = { (_, frame) in + self.currentFrame = frame + } + self.imagePlayer = imagePlayer + imagePlayer.startPlaying() + } + } + } +} + #if DEBUG struct WebImage_Previews : PreviewProvider { static var previews: some View { diff --git a/SDWebImageSwiftUI/Module/SDWebImageSwiftUI.h b/SDWebImageSwiftUI/Module/SDWebImageSwiftUI.h index d65950d..87996dd 100644 --- a/SDWebImageSwiftUI/Module/SDWebImageSwiftUI.h +++ b/SDWebImageSwiftUI/Module/SDWebImageSwiftUI.h @@ -15,3 +15,4 @@ FOUNDATION_EXPORT double SDWebImageSwiftUIVersionNumber; FOUNDATION_EXPORT const unsigned char SDWebImageSwiftUIVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import +#import