Skip to content

Add the same overload method for onSuccess API, which introduce the image data arg. Keep the source code compatibility #109

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 2 commits into from
May 7, 2020
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 31 additions & 6 deletions SDWebImageSwiftUI/Classes/AnimatedImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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) {
Expand All @@ -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())
Expand Down Expand Up @@ -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
}
Expand Down
27 changes: 24 additions & 3 deletions SDWebImageSwiftUI/Classes/ImageManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)?

Expand Down Expand Up @@ -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())
}
Expand Down Expand Up @@ -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
}

Expand Down
25 changes: 24 additions & 1 deletion SDWebImageSwiftUI/Classes/WebImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
5 changes: 3 additions & 2 deletions Tests/AnimatedImageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ class AnimatedImageTests: XCTestCase {
let expectation = self.expectation(description: "AnimatedImage url initializer")
let imageUrl = URL(string: "https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif")
let imageView = AnimatedImage(url: imageUrl)
.onSuccess { image, cacheType in
.onSuccess { image, data, cacheType in
XCTAssertNotNil(image)
if let animatedImage = image as? SDAnimatedImage {
XCTAssertEqual(animatedImage.animatedImageLoopCount, 0)
XCTAssertEqual(animatedImage.animatedImageFrameCount, 389)
Expand All @@ -88,7 +89,7 @@ class AnimatedImageTests: XCTestCase {
ViewHosting.host(view: imageView)
let animatedImageView = try imageView.inspect().actualView().platformView().wrapped
XCTAssertEqual(animatedImageView.sd_imageURL, imageUrl)
self.waitForExpectations(timeout: 5, handler: nil)
self.waitForExpectations(timeout: 10, handler: nil)
ViewHosting.expel()
}

Expand Down
31 changes: 25 additions & 6 deletions Tests/WebImageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class WebImageTests: XCTestCase {
let expectation = self.expectation(description: "WebImage static url initializer")
let imageUrl = URL(string: "https://nr-platform.s3.amazonaws.com/uploads/platform/published_extension/branding_icon/275/AmazonS3.png")
let imageView = WebImage(url: imageUrl)
let introspectView = imageView.onSuccess { image, cacheType in
let introspectView = imageView.onSuccess { image, data, cacheType in
#if os(iOS) || os(tvOS)
let displayImage = try? imageView.inspect().group().image(0).uiImage()
#else
Expand All @@ -43,7 +43,7 @@ class WebImageTests: XCTestCase {
let imageUrl = URL(string: "https://apng.onevcat.com/assets/elephant.png")
let binding = Binding<Bool>(wrappedValue: true)
let imageView = WebImage(url: imageUrl, isAnimating: binding)
let introspectView = imageView.onSuccess { image, cacheType in
let introspectView = imageView.onSuccess { image, data, cacheType in
if let animatedImage = image as? SDAnimatedImage {
XCTAssertTrue(imageView.isAnimating)
#if os(iOS) || os(tvOS)
Expand Down Expand Up @@ -73,7 +73,7 @@ class WebImageTests: XCTestCase {
let imageUrl = URL(string: "https://raw.githubusercontent.com/ibireme/YYImage/master/Demo/YYImageDemo/mew_baseline.jpg")
let imageView = WebImage(url: imageUrl, options: [.progressiveLoad], context: [.imageScaleFactor: 1])
let introspectView = imageView
.onSuccess { _, _ in
.onSuccess { _, _, _ in
expectation.fulfill()
}
.onFailure { _ in
Expand Down Expand Up @@ -111,7 +111,7 @@ class WebImageTests: XCTestCase {
}

func testWebImageOnSuccessWhenMemoryCacheHit() throws {
let expectation = self.expectation(description: "WebImage onSuccess when memory hit")
let expectation = self.expectation(description: "WebImage onSuccess when memory cache hit")
let imageUrl = URL(string: "https://foo.bar/buzz.png")
let cacheKey = SDWebImageManager.shared.cacheKey(for: imageUrl)
#if os(macOS)
Expand All @@ -121,7 +121,7 @@ class WebImageTests: XCTestCase {
#endif
SDImageCache.shared.storeImage(toMemory: testImage, forKey: cacheKey)
let imageView = WebImage(url: imageUrl)
let introspectView = imageView.onSuccess { image, cacheType in
let introspectView = imageView.onSuccess { image, data, cacheType in
XCTAssert(cacheType == .memory)
XCTAssertNotNil(image)
XCTAssertEqual(image, testImage)
Expand All @@ -133,12 +133,31 @@ class WebImageTests: XCTestCase {
ViewHosting.expel()
}

func testWebImageOnSuccessWhenCacheMiss() throws {
let expectation = self.expectation(description: "WebImage onSuccess when cache miss")
let imageUrl = URL(string: "http://via.placeholder.com/100x100.png")
let cacheKey = SDWebImageManager.shared.cacheKey(for: imageUrl)
SDImageCache.shared.removeImageFromMemory(forKey: cacheKey)
SDImageCache.shared.removeImageFromDisk(forKey: cacheKey)
let imageView = WebImage(url: imageUrl)
let introspectView = imageView.onSuccess { image, data, cacheType in
XCTAssert(cacheType == .none)
XCTAssertNotNil(image)
XCTAssertNotNil(data)
expectation.fulfill()
}
_ = try introspectView.inspect()
ViewHosting.host(view: introspectView)
self.waitForExpectations(timeout: 5, handler: nil)
ViewHosting.expel()
}

func testWebImageEXIFImage() throws {
let expectation = self.expectation(description: "WebImage EXIF image url")
// EXIF 5, Left Mirrored
let imageUrl = URL(string: "https://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Landscape_5.jpg")
let imageView = WebImage(url: imageUrl)
let introspectView = imageView.onSuccess { image, cacheType in
let introspectView = imageView.onSuccess { image, data, cacheType in
let displayImage = try? imageView.inspect().group().image(0).cgImage()
let orientation = try! imageView.inspect().group().image(0).orientation()
XCTAssertNotNil(displayImage)
Expand Down