diff --git a/README.md b/README.md index d19fe8d..3377bde 100644 --- a/README.md +++ b/README.md @@ -111,8 +111,9 @@ github "SDWebImage/SDWebImageSwiftUI" var body: some View { WebImage(url: URL(string: "https://nokiatech.github.io/heif/content/images/ski_jump_1440x960.heic")) // Supports options and context, like `.delayPlaceholder` to show placeholder only when error - .onSuccess { image, cacheType in + .onSuccess { image, data, cacheType in // Success + // Note: Data exist only when queried from disk cache or network. Use `.queryMemoryData` if you really need data } .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size .placeholder(Image(systemName: "photo")) // Placeholder Image diff --git a/SDWebImageSwiftUI/Classes/AnimatedImage.swift b/SDWebImageSwiftUI/Classes/AnimatedImage.swift index 81a8b88..b9e8b9b 100644 --- a/SDWebImageSwiftUI/Classes/AnimatedImage.swift +++ b/SDWebImageSwiftUI/Classes/AnimatedImage.swift @@ -49,7 +49,7 @@ final class AnimatedLoadingModel : ObservableObject, IndicatorReportable { @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) final class AnimatedImageHandler: ObservableObject { // Completion Handler - @Published var successBlock: ((PlatformImage, SDImageCacheType) -> Void)? + @Published var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)? @Published var failureBlock: ((Error) -> Void)? @Published var progressBlock: ((Int, Int) -> Void)? // Coordinator Handler @@ -208,12 +208,15 @@ public struct AnimatedImage : PlatformViewRepresentable { return } self.imageLoading.isLoading = true - if imageModel.webOptions.contains(.delayPlaceholder) { + let options = imageModel.webOptions + if options.contains(.delayPlaceholder) { self.imageConfiguration.placeholderView?.isHidden = true } else { self.imageConfiguration.placeholderView?.isHidden = false } - view.wrapped.sd_setImage(with: imageModel.url, placeholderImage: imageConfiguration.placeholder, options: imageModel.webOptions, context: imageModel.webContext, progress: { (receivedSize, expectedSize, _) in + var context = imageModel.webContext ?? [:] + context[.animatedImageClass] = SDAnimatedImage.self + view.wrapped.sd_internalSetImage(with: imageModel.url, placeholderImage: imageConfiguration.placeholder, options: options, context: context, setImageBlock: nil, progress: { (receivedSize, expectedSize, _) in let progress: Double if (expectedSize > 0) { progress = Double(receivedSize) / Double(expectedSize) @@ -224,7 +227,7 @@ public struct AnimatedImage : PlatformViewRepresentable { self.imageLoading.progress = progress } self.imageHandler.progressBlock?(receivedSize, expectedSize) - }) { (image, error, cacheType, _) in + }) { (image, data, error, cacheType, finished, _) in // This is a hack because of Xcode 11.3 bug, the @Published does not trigger another `updateUIView` call // Here I have to use UIKit/AppKit API to triger the same effect (the window change implicitly cause re-render) if let hostingView = AnimatedImage.findHostingView(from: view) { @@ -241,7 +244,7 @@ public struct AnimatedImage : PlatformViewRepresentable { self.imageLoading.progress = 1 if let image = image { self.imageConfiguration.placeholderView?.isHidden = true - self.imageHandler.successBlock?(image, cacheType) + self.imageHandler.successBlock?(image, data, cacheType) } else { self.imageConfiguration.placeholderView?.isHidden = false self.imageHandler.failureBlock?(error ?? NSError()) @@ -702,11 +705,33 @@ extension AnimatedImage { return self } + /// Provide the action when image load successes. + /// - Parameters: + /// - action: The action to perform. The first arg is the loaded image. If `action` is `nil`, the call has no effect. + /// - Returns: A view that triggers `action` when this image load successes. + public func onSuccess(perform action: @escaping (PlatformImage) -> Void) -> AnimatedImage { + self.imageHandler.successBlock = { image, _, _ in + action(image) + } + return self + } + /// Provide the action when image load successes. /// - Parameters: /// - action: The action to perform. The first arg is the loaded image, the second arg is the cache type loaded from. If `action` is `nil`, the call has no effect. /// - Returns: A view that triggers `action` when this image load successes. - public func onSuccess(perform action: ((PlatformImage, SDImageCacheType) -> Void)? = nil) -> AnimatedImage { + public func onSuccess(perform action: @escaping (PlatformImage, SDImageCacheType) -> Void) -> AnimatedImage { + self.imageHandler.successBlock = { image, _, cacheType in + action(image, cacheType) + } + return self + } + + /// Provide the action when image load successes. + /// - Parameters: + /// - action: The action to perform. The first arg is the loaded image, the second arg is the loaded image data, the third arg is the cache type loaded from. If `action` is `nil`, the call has no effect. + /// - Returns: A view that triggers `action` when this image load successes. + public func onSuccess(perform action: ((PlatformImage, Data?, SDImageCacheType) -> Void)? = nil) -> AnimatedImage { self.imageHandler.successBlock = action return self } diff --git a/SDWebImageSwiftUI/Classes/ImageManager.swift b/SDWebImageSwiftUI/Classes/ImageManager.swift index e115d50..f7416e3 100644 --- a/SDWebImageSwiftUI/Classes/ImageManager.swift +++ b/SDWebImageSwiftUI/Classes/ImageManager.swift @@ -17,6 +17,8 @@ public final class ImageManager : ObservableObject { @Published public var image: PlatformImage? /// loaded image data, may be nil if hit from memory cache. This will only published once even on incremental image loading @Published public var imageData: Data? + /// loaded image cache type, .none means from network + @Published public var cacheType: SDImageCacheType = .none /// loading error, you can grab the error code and reason listed in `SDWebImageErrorDomain`, to provide a user interface about the error reason @Published public var error: Error? /// whether network is loading or cache is querying, should only be used for indicator binding @@ -33,7 +35,7 @@ public final class ImageManager : ObservableObject { var url: URL? var options: SDWebImageOptions var context: [SDWebImageContextOption : Any]? - var successBlock: ((PlatformImage, SDImageCacheType) -> Void)? + var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)? var failureBlock: ((Error) -> Void)? var progressBlock: ((Int, Int) -> Void)? @@ -89,10 +91,11 @@ public final class ImageManager : ObservableObject { self.isIncremental = !finished if finished { self.imageData = data + self.cacheType = cacheType self.isLoading = false self.progress = 1 if let image = image { - self.successBlock?(image, cacheType) + self.successBlock?(image, data, cacheType) } else { self.failureBlock?(error ?? NSError()) } @@ -120,10 +123,28 @@ extension ImageManager { self.failureBlock = action } + /// Provide the action when image load successes. + /// - Parameters: + /// - action: The action to perform. The first arg is the loaded image. If `action` is `nil`, the call has no effect. + public func setOnSuccess(perform action: @escaping (PlatformImage) -> Void) { + self.successBlock = { image, _, _ in + action(image) + } + } + /// Provide the action when image load successes. /// - Parameters: /// - action: The action to perform. The first arg is the loaded image, the second arg is the cache type loaded from. If `action` is `nil`, the call has no effect. - public func setOnSuccess(perform action: ((PlatformImage, SDImageCacheType) -> Void)? = nil) { + public func setOnSuccess(perform action: @escaping (PlatformImage, SDImageCacheType) -> Void) { + self.successBlock = { image, _, cacheType in + action(image, cacheType) + } + } + + /// Provide the action when image load successes. + /// - Parameters: + /// - action: The action to perform. The first arg is the loaded image, the second arg is the loaded image data, the third arg is the cache type loaded from. If `action` is `nil`, the call has no effect. + public func setOnSuccess(perform action: ((PlatformImage, Data?, SDImageCacheType) -> Void)? = nil) { self.successBlock = action } diff --git a/SDWebImageSwiftUI/Classes/WebImage.swift b/SDWebImageSwiftUI/Classes/WebImage.swift index df53c29..96b8954 100644 --- a/SDWebImageSwiftUI/Classes/WebImage.swift +++ b/SDWebImageSwiftUI/Classes/WebImage.swift @@ -264,11 +264,34 @@ extension WebImage { return self } + /// Provide the action when image load successes. + /// - Parameters: + /// - action: The action to perform. The first arg is the loaded image. If `action` is `nil`, the call has no effect. + /// - Returns: A view that triggers `action` when this image load successes. + public func onSuccess(perform action: @escaping (PlatformImage) -> Void) -> WebImage { + let action = action + self.imageManager.successBlock = { image, _, _ in + action(image) + } + return self + } + /// Provide the action when image load successes. /// - Parameters: /// - action: The action to perform. The first arg is the loaded image, the second arg is the cache type loaded from. If `action` is `nil`, the call has no effect. /// - Returns: A view that triggers `action` when this image load successes. - public func onSuccess(perform action: ((PlatformImage, SDImageCacheType) -> Void)? = nil) -> WebImage { + public func onSuccess(perform action: @escaping (PlatformImage, SDImageCacheType) -> Void) -> WebImage { + self.imageManager.successBlock = { image, _, cacheType in + action(image, cacheType) + } + return self + } + + /// Provide the action when image load successes. + /// - Parameters: + /// - action: The action to perform. The first arg is the loaded image, the second arg is the loaded image data, the third arg is the cache type loaded from. If `action` is `nil`, the call has no effect. + /// - Returns: A view that triggers `action` when this image load successes. + public func onSuccess(perform action: ((PlatformImage, Data?, SDImageCacheType) -> Void)? = nil) -> WebImage { self.imageManager.successBlock = action return self }