Merge pull request #275 from SDWebImage/api/asyncimage

Update the WebImage API to match SwiftUI.AsyncImage
This commit is contained in:
DreamPiggy 2023-10-21 18:33:08 +08:00 committed by GitHub
commit eb0eafa520
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 164 additions and 194 deletions

View File

@ -96,22 +96,19 @@ struct ContentView: View {
HStack {
if self.animated {
#if os(macOS) || os(iOS) || os(tvOS) || os(visionOS)
AnimatedImage(url: URL(string:url), isAnimating: .constant(true))
AnimatedImage(url: URL(string:url))
.onViewUpdate { view, context in
#if os(macOS)
view.toolTip = url
#endif
}
.indicator(SDWebImageActivityIndicator.medium)
/**
.placeholder(UIImage(systemName: "photo"))
*/
.indicator(.activity)
.transition(.fade)
.resizable()
.scaledToFit()
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
#else
WebImage(url: URL(string:url), isAnimating: self.$animated)
WebImage(url: URL(string:url))
.resizable()
.indicator(.activity)
.transition(.fade(duration: 0.5))
@ -119,13 +116,8 @@ struct ContentView: View {
.frame(width: CGFloat(100), height: CGFloat(100), alignment: .center)
#endif
} else {
WebImage(url: URL(string:url), isAnimating: .constant(true))
WebImage(url: URL(string:url))
.resizable()
/**
.placeholder {
Image(systemName: "photo")
}
*/
.indicator(.activity)
.transition(.fade(duration: 0.5))
.scaledToFit()
@ -199,15 +191,16 @@ struct ContentView: View {
}
#endif
#if os(watchOS)
return contentView()
.contextMenu {
Button(action: { self.reloadCache() }) {
Text("Reload")
return NavigationView {
contentView()
.navigationTitle("WebImage")
.toolbar {
Button(action: { self.reloadCache() }) {
Text("Reload")
}
}
Button(action: { self.switchView() }) {
Text("Switch")
}
}
}
#endif
}

View File

@ -52,10 +52,8 @@ struct DetailView: View {
#endif
#if os(macOS) || os(watchOS)
zoomView()
.contextMenu {
Button(isAnimating ? "Stop" : "Start") {
self.isAnimating.toggle()
}
.onTapGesture {
self.isAnimating.toggle()
}
#endif
}
@ -95,24 +93,31 @@ struct DetailView: View {
HStack {
if animated {
#if os(macOS) || os(iOS) || os(tvOS) || os(visionOS)
AnimatedImage(url: URL(string:url), options: [.progressiveLoad, .delayPlaceholder], isAnimating: $isAnimating)
AnimatedImage(url: URL(string:url), options: [.progressiveLoad, .delayPlaceholder], isAnimating: $isAnimating, placeholderImage: .wifiExclamationmark)
.indicator(.progress)
.resizable()
.placeholder(.wifiExclamationmark)
.indicator(SDWebImageProgressIndicator.default)
.scaledToFit()
#else
WebImage(url: URL(string:url), options: [.progressiveLoad, .delayPlaceholder], isAnimating: $isAnimating)
.resizable()
.placeholder(.wifiExclamationmark)
WebImage(url: URL(string:url), options: [.progressiveLoad, .delayPlaceholder], isAnimating: $isAnimating) { image in
image.resizable()
.scaledToFit()
} placeholder: {
Image.wifiExclamationmark
.resizable()
.scaledToFit()
}
.indicator(.progress)
.scaledToFit()
#endif
} else {
WebImage(url: URL(string:url), options: [.progressiveLoad, .delayPlaceholder], isAnimating: $isAnimating)
.resizable()
.placeholder(.wifiExclamationmark)
WebImage(url: URL(string:url), options: [.progressiveLoad, .delayPlaceholder], isAnimating: $isAnimating) { image in
image.resizable()
.scaledToFit()
} placeholder: {
Image.wifiExclamationmark
.resizable()
.scaledToFit()
}
.indicator(.progress(style: .circular))
.scaledToFit()
}
}
}

View File

@ -128,18 +128,16 @@ github "SDWebImage/SDWebImageSwiftUI"
```swift
var body: some View {
WebImage(url: URL(string: "https://nokiatech.github.io/heif/content/images/ski_jump_1440x960.heic"))
WebImage(url: URL(string: "https://nokiatech.github.io/heif/content/images/ski_jump_1440x960.heic")) { image in
image.resizable() // Control layout like SwiftUI.AsyncImage, you must use this modifier or the view will use the image bitmap size
} placeholder: {
Rectangle().foregroundColor(.gray)
}
// Supports options and context, like `.delayPlaceholder` to show placeholder only when error
.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
// Supports ViewBuilder as well
.placeholder {
Rectangle().foregroundColor(.gray)
}
.indicator(.activity) // Activity Indicator
.transition(.fade(duration: 0.5)) // Fade Transition with duration
.scaledToFit()
@ -194,21 +192,21 @@ WebImage(url: url)
```swift
var body: some View {
Group {
AnimatedImage(url: URL(string: "https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif"))
AnimatedImage(url: URL(string: "https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif"), placeholderImage: .init(systemName: "photo")) // Placeholder Image
// Supports options and context, like `.progressiveLoad` for progressive animation loading
.onFailure { error in
// Error
}
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
.placeholder(UIImage(systemName: "photo")) // Placeholder Image
// Supports ViewBuilder as well
.placeholder {
Circle().foregroundColor(.gray)
}
.indicator(SDWebImageActivityIndicator.medium) // Activity Indicator
.indicator(.activity) // Activity Indicator
.transition(.fade) // Fade Transition
.scaledToFit() // Attention to call it on AnimatedImage, but not `some View` after View Modifier (Swift Protocol Extension method is static dispatched)
// Supports SwiftUI ViewBuilder placeholder as well
AnimatedImage(url: url) {
Circle().foregroundColor(.gray)
}
// Data
AnimatedImage(data: try! Data(contentsOf: URL(fileURLWithPath: "/tmp/foo.webp")))
.customLoopCount(1) // Custom loop count
@ -624,8 +622,8 @@ Since SwiftUI is aimed to support all Apple platforms, our demo does this as wel
Demo Tips:
1. Use `Switch` (right-click on macOS/force press on watchOS) to switch between `WebImage` and `AnimatedImage`.
2. Use `Reload` (right-click on macOS/force press on watchOS) to clear cache.
1. Use `Switch` (right-click on macOS/tap on watchOS) to switch between `WebImage` and `AnimatedImage`.
2. Use `Reload` (right-click on macOS/button on watchOS) to clear cache.
3. Use `Swipe Left` (menu button on tvOS) to delete one image url from list.
4. Pinch gesture (Digital Crown on watchOS, play button on tvOS) to zoom-in detail page image.
5. Clear cache and go to detail page to see progressive loading.

View File

@ -31,6 +31,12 @@ final class AnimatedImageModel : ObservableObject {
@Published var url: URL?
@Published var webOptions: SDWebImageOptions = []
@Published var webContext: [SDWebImageContextOption : Any]? = nil
@Published var placeholderImage: PlatformImage?
@Published var placeholderView: PlatformView? {
didSet {
oldValue?.removeFromSuperview()
}
}
/// Name image
@Published var name: String?
@Published var bundle: Bundle?
@ -90,12 +96,6 @@ final class AnimatedImageConfiguration: ObservableObject {
// These configurations only useful for web image loading
var indicator: SDWebImageIndicator?
var transition: SDWebImageTransition?
var placeholder: PlatformImage?
var placeholderView: PlatformView? {
didSet {
oldValue?.removeFromSuperview()
}
}
}
/// A Image View type to load image from url, data or bundle. Supports animated and static image format.
@ -115,13 +115,19 @@ public struct AnimatedImage : PlatformViewRepresentable {
/// True to start animation, false to stop animation.
@Binding public var isAnimating: Bool
/// Create an animated image with url, placeholder, custom options and context.
/// Create an animated image with url, placeholder, custom options and context, including animation control binding.
/// - Parameter url: The image url
/// - Parameter placeholder: The placeholder image to show during loading
/// - 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.init(url: url, options: options, context: context, isAnimating: .constant(true))
/// - Parameter isAnimating: The binding for animation control
public init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding<Bool> = .constant(true), placeholderImage: PlatformImage? = nil) {
let imageModel = AnimatedImageModel()
imageModel.url = url
imageModel.webOptions = options
imageModel.webContext = context
imageModel.placeholderImage = placeholderImage
self.init(imageModel: imageModel, isAnimating: isAnimating)
}
/// Create an animated image with url, placeholder, custom options and context, including animation control binding.
@ -130,46 +136,37 @@ public struct AnimatedImage : PlatformViewRepresentable {
/// - 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.
/// - Parameter isAnimating: The binding for animation control
public init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding<Bool>) {
public init<T>(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding<Bool> = .constant(true), @ViewBuilder placeholder: @escaping () -> T) where T : View {
let imageModel = AnimatedImageModel()
imageModel.url = url
imageModel.webOptions = options
imageModel.webContext = context
#if os(macOS)
let hostingView = NSHostingView(rootView: placeholder())
#else
let hostingView = _UIHostingView(rootView: placeholder())
#endif
imageModel.placeholderView = hostingView
self.init(imageModel: imageModel, isAnimating: isAnimating)
}
/// Create an animated image with name and bundle.
/// - Note: Asset Catalog is not supported.
/// - Parameter name: The image name
/// - Parameter bundle: The bundle contains image
public init(name: String, bundle: Bundle? = nil) {
self.init(name: name, bundle: bundle, isAnimating: .constant(true))
}
/// Create an animated image with name and bundle, including animation control binding.
/// - Note: Asset Catalog is not supported.
/// - Parameter name: The image name
/// - Parameter bundle: The bundle contains image
/// - Parameter isAnimating: The binding for animation control
public init(name: String, bundle: Bundle? = nil, isAnimating: Binding<Bool>) {
public init(name: String, bundle: Bundle? = nil, isAnimating: Binding<Bool> = .constant(true)) {
let imageModel = AnimatedImageModel()
imageModel.name = name
imageModel.bundle = bundle
self.init(imageModel: imageModel, isAnimating: isAnimating)
}
/// Create an animated image with data and scale.
/// - Parameter data: The image data
/// - Parameter scale: The scale factor
public init(data: Data, scale: CGFloat = 1) {
self.init(data: data, scale: scale, isAnimating: .constant(true))
}
/// Create an animated image with data and scale, including animation control binding.
/// - Parameter data: The image data
/// - Parameter scale: The scale factor
/// - Parameter isAnimating: The binding for animation control
public init(data: Data, scale: CGFloat = 1, isAnimating: Binding<Bool>) {
public init(data: Data, scale: CGFloat = 1, isAnimating: Binding<Bool> = .constant(true)) {
let imageModel = AnimatedImageModel()
imageModel.data = data
imageModel.scale = scale
@ -222,7 +219,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
func setupIndicator(_ view: AnimatedImageViewWrapper, context: Context) {
view.wrapped.sd_imageIndicator = imageConfiguration.indicator
view.wrapped.sd_imageTransition = imageConfiguration.transition
if let placeholderView = imageConfiguration.placeholderView {
if let placeholderView = imageModel.placeholderView {
placeholderView.removeFromSuperview()
placeholderView.isHidden = true
// Placeholder View should below the Indicator View
@ -243,13 +240,13 @@ public struct AnimatedImage : PlatformViewRepresentable {
context.coordinator.imageLoading.isLoading = true
let webOptions = imageModel.webOptions
if webOptions.contains(.delayPlaceholder) {
self.imageConfiguration.placeholderView?.isHidden = true
self.imageModel.placeholderView?.isHidden = true
} else {
self.imageConfiguration.placeholderView?.isHidden = false
self.imageModel.placeholderView?.isHidden = false
}
var webContext = imageModel.webContext ?? [:]
webContext[.animatedImageClass] = SDAnimatedImage.self
view.wrapped.sd_internalSetImage(with: imageModel.url, placeholderImage: imageConfiguration.placeholder, options: webOptions, context: webContext, setImageBlock: nil, progress: { (receivedSize, expectedSize, _) in
view.wrapped.sd_internalSetImage(with: imageModel.url, placeholderImage: imageModel.placeholderImage, options: webOptions, context: webContext, setImageBlock: nil, progress: { (receivedSize, expectedSize, _) in
let progress: Double
if (expectedSize > 0) {
progress = Double(receivedSize) / Double(expectedSize)
@ -265,10 +262,10 @@ public struct AnimatedImage : PlatformViewRepresentable {
context.coordinator.imageLoading.isLoading = false
context.coordinator.imageLoading.progress = 1
if let image = image {
self.imageConfiguration.placeholderView?.isHidden = true
self.imageModel.placeholderView?.isHidden = true
self.imageHandler.successBlock?(image, data, cacheType)
} else {
self.imageConfiguration.placeholderView?.isHidden = false
self.imageModel.placeholderView?.isHidden = false
self.imageHandler.failureBlock?(error ?? NSError())
}
}
@ -794,30 +791,19 @@ extension AnimatedImage {
}
}
// Convenient indicator dot syntax
extension SDWebImageIndicator where Self == SDWebImageActivityIndicator {
public static var activity: Self { Self() }
}
extension SDWebImageIndicator where Self == SDWebImageProgressIndicator {
public static var progress: Self { Self() }
}
// Web Image convenience, based on UIKit/AppKit API
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
extension AnimatedImage {
/// Associate a placeholder when loading image with url
/// - Parameter content: A view that describes the placeholder.
/// - note: The differences between this and placeholder image, it's that placeholder image replace the image for image view, but this modify the View Hierarchy to overlay the placeholder hosting view
public func placeholder<T>(@ViewBuilder content: () -> T) -> AnimatedImage where T : View {
#if os(macOS)
let hostingView = NSHostingView(rootView: content())
#else
let hostingView = _UIHostingView(rootView: content())
#endif
self.imageConfiguration.placeholderView = hostingView
return self
}
/// Associate a placeholder image when loading image with url
/// - Parameter content: A view that describes the placeholder.
public func placeholder(_ image: PlatformImage?) -> AnimatedImage {
self.imageConfiguration.placeholder = image
return self
}
/// Associate a indicator when loading image with url
/// - Note: If you do not need indicator, specify nil. Defaults to nil
/// - Parameter indicator: indicator, see more in `SDWebImageIndicator`
@ -835,23 +821,6 @@ extension AnimatedImage {
}
}
// Indicator
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
extension AnimatedImage {
/// Associate a indicator when loading image with url
/// - Parameter indicator: The indicator type, see `Indicator`
public func indicator<T>(_ indicator: Indicator<T>) -> some View where T : View {
return self.modifier(IndicatorViewModifier(status: indicatorStatus, indicator: indicator))
}
/// Associate a indicator when loading image with url, convenient method with block
/// - Parameter content: A view that describes the indicator.
public func indicator<T>(@ViewBuilder content: @escaping (_ isAnimating: Binding<Bool>, _ progress: Binding<Double>) -> T) -> some View where T : View {
return indicator(Indicator(content: content))
}
}
#if DEBUG
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
struct AnimatedImage_Previews : PreviewProvider {

View File

@ -9,6 +9,43 @@
import SwiftUI
import SDWebImage
public enum WebImagePhase {
/// No image is loaded.
case empty
/// An image succesfully loaded.
case success(Image)
/// An image failed to load with an error.
case failure(Error)
/// The loaded image, if any.
///
/// If this value isn't `nil`, the image load operation has finished,
/// and you can use the image to update the view. You can use the image
/// directly, or you can modify it in some way. For example, you can add
/// a ``Image/resizable(capInsets:resizingMode:)`` modifier to make the
/// image resizable.
public var image: Image? {
switch self {
case let .success(image):
return image
case .empty, .failure:
return nil
}
}
/// The error that occurred when attempting to load an image, if any.
public var error: Error? {
switch self {
case .empty, .success:
return nil
case let .failure(error):
return error
}
}
}
/// Data Binding Object, only properties in this object can support changes from user with @State and refresh
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
final class WebImageModel : ObservableObject {
@ -43,10 +80,12 @@ final class WebImageConfiguration: ObservableObject {
/// A Image View type to load image from url. Supports static/animated image format.
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
public struct WebImage : View {
public struct WebImage<Content> : View where Content: View {
var transaction: Transaction
var configurations: [(Image) -> Image] = []
var placeholder: AnyView?
var content: (WebImagePhase) -> Content
/// A Binding to control the animation. You can bind external logic to control the animation status.
/// True to start animation, false to stop animation.
@ -72,7 +111,23 @@ public struct WebImage : View {
/// - 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.
/// - Parameter isAnimating: The binding for animation control. The binding value should be `true` when initialized to setup the correct animated image class. If not, you must provide the `.animatedImageClass` explicitly. When the animation started, this binding can been used to start / stop the animation.
public init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding<Bool>) {
public init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding<Bool> = .constant(true)) where Content == Image {
self.init(url: url, options: options, context: context, isAnimating: isAnimating) { phase in
phase.image ?? Image(platformImage: .empty)
}
}
public init<I, P>(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding<Bool> = .constant(true), @ViewBuilder content: @escaping (Image) -> I, @ViewBuilder placeholder: @escaping () -> P) where Content == _ConditionalContent<I, P>, I: View, P: View {
self.init(url: url, options: options, context: context, isAnimating: isAnimating) { phase in
if let i = phase.image {
content(i)
} else {
placeholder()
}
}
}
public init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding<Bool> = .constant(true), transaction: Transaction = Transaction(), @ViewBuilder content: @escaping (WebImagePhase) -> Content) {
self._isAnimating = isAnimating
var context = context ?? [:]
// provide animated image class if the initialized `isAnimating` is true, user can still custom the image class if they want
@ -89,27 +144,16 @@ public struct WebImage : View {
let imageManager = ImageManager()
_imageManager = StateObject(wrappedValue: imageManager)
_indicatorStatus = ObservedObject(wrappedValue: imageManager.indicatorStatus)
}
/// 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.init(url: url, options: options, context: context, isAnimating: .constant(true))
self.transaction = transaction
self.content = { phase in
content(phase)
}
}
public var body: some View {
// Container
return ZStack {
// This empty Image is used to receive container's level appear/disappear to start/stop player, reduce CPU usage
Image(platformImage: .empty)
.onAppear {
self.appearAction()
}
.onDisappear {
self.disappearAction()
}
// Render Logic for actual animated image frame or static image
if imageManager.image != nil && imageModel.url == imageManager.currentURL {
if isAnimating && !imageManager.isIncremental {
@ -118,8 +162,8 @@ public struct WebImage : View {
displayImage()
}
} else {
content((imageManager.error != nil) ? .failure(imageManager.error!) : .empty)
// Load Logic
setupPlaceholder()
.onPlatformAppear(appear: {
self.setupManager()
if (self.imageManager.error == nil) {
@ -145,7 +189,7 @@ public struct WebImage : View {
/// Configure the platform image into the SwiftUI rendering image
func configure(image: PlatformImage) -> some View {
// Actual rendering SwiftUI image
let result: Image
var result: Image
// NSImage works well with SwiftUI, include Vector and EXIF images.
#if os(macOS)
result = Image(nsImage: image)
@ -188,9 +232,12 @@ public struct WebImage : View {
// Should not use `EmptyView`, which does not respect to the container's frame modifier
// Using a empty image instead for better compatible
return configurations.reduce(result) { (previous, configuration) in
let i = configurations.reduce(result) { (previous, configuration) in
configuration(previous)
}
// Apply view builder
return content(.success(i))
}
/// Image Manager status
@ -279,25 +326,6 @@ public struct WebImage : View {
}
}
}
/// Placeholder View Support
func setupPlaceholder() -> some View {
// Don't use `Group` because it will trigger `.onAppear` and `.onDisappear` when condition view removed, treat placeholder as an entire component
let result: AnyView
if let placeholder = placeholder {
// If use `.delayPlaceholder`, the placeholder is applied after loading failed, hide during loading :)
if imageModel.options.contains(.delayPlaceholder) && imageManager.error == nil {
result = AnyView(configure(image: .empty))
} else {
result = placeholder
}
} else {
result = AnyView(configure(image: .empty))
}
// Custom ID to avoid SwiftUI engine cache the status, and does not call `onAppear` when placeholder not changed (See `ContentView.swift/ContentView2` case)
// Because we load the image url in placeholder's `onAppear`, it should be called to sync with state changes :)
return result.id(imageModel.url)
}
}
// Layout
@ -373,27 +401,6 @@ extension WebImage {
// WebImage Modifier
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
extension WebImage {
/// Associate a placeholder when loading image with url
/// - note: The differences between Placeholder and Indicator, is that placeholder does not supports animation, and return type is different
/// - Parameter content: A view that describes the placeholder.
public func placeholder<T>(@ViewBuilder content: () -> T) -> WebImage where T : View {
var result = self
result.placeholder = AnyView(content())
return result
}
/// Associate a placeholder image when loading image with url
/// - note: This placeholder image will apply the same size and resizable from WebImage for convenience. If you don't want this, use the ViewBuilder one above instead
/// - Parameter image: A Image view that describes the placeholder.
public func placeholder(_ image: Image) -> WebImage {
return placeholder {
configurations.reduce(image) { (previous, configuration) in
configuration(previous)
}
}
}
/// Control the behavior to retry the failed loading when view become appears again
/// - Parameter flag: Whether or not to retry the failed loading
public func retryOnAppear(_ flag: Bool) -> WebImage {

View File

@ -142,7 +142,9 @@ class AnimatedImageTests: XCTestCase {
func testAnimatedImageModifier() throws {
let expectation = self.expectation(description: "WebImage modifier")
let imageUrl = URL(string: "https://assets.sbnation.com/assets/2512203/dogflops.gif")
let imageView = AnimatedImage(url: imageUrl, options: [.progressiveLoad], context: [.imageScaleFactor: 1])
let imageView = AnimatedImage(url: imageUrl, options: [.progressiveLoad], context: [.imageScaleFactor: 1]) {
Circle()
}
let introspectView = imageView
.onSuccess { _, _, _ in
expectation.fulfill()
@ -161,11 +163,7 @@ class AnimatedImageTests: XCTestCase {
XCTAssert(view.isKind(of: SDAnimatedImageView.self))
XCTAssertEqual(context.coordinator.userInfo?["foo"] as? String, "bar")
}
.placeholder(PlatformImage())
.placeholder {
Circle()
}
.indicator(SDWebImageActivityIndicator.medium)
.indicator(.activity)
// Image
.resizable()
.renderingMode(.original)

View File

@ -73,7 +73,11 @@ class WebImageTests: XCTestCase {
func testWebImageModifier() throws {
let expectation = self.expectation(description: "WebImage modifier")
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 imageView = WebImage(url: imageUrl, options: [.progressiveLoad], context: [.imageScaleFactor: 1]) { image in
image.resizable()
} placeholder: {
Circle()
}
let introspectView = imageView
.onSuccess { _, _, _ in
expectation.fulfill()
@ -83,10 +87,6 @@ class WebImageTests: XCTestCase {
}
.onProgress { _, _ in
}
.placeholder(.init(platformImage: PlatformImage()))
.placeholder {
Circle()
}
// Image
.resizable()