Merge pull request #304 from SDWebImage/bugfix/nil_url
Fix the issue for WebImage/AnimatedImage when url is nil will not cause the reloading
This commit is contained in:
commit
54199185d8
|
@ -17,6 +17,43 @@ class UserSettings: ObservableObject {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test Switching nil url
|
||||||
|
struct ContentView3: View {
|
||||||
|
@State var isOn = false
|
||||||
|
@State var animated: Bool = false // You can change between WebImage/AnimatedImage
|
||||||
|
|
||||||
|
var url: URL? {
|
||||||
|
if isOn {
|
||||||
|
.init(string: "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Google_%22G%22_logo.svg/1024px-Google_%22G%22_logo.svg.png")
|
||||||
|
} else {
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Text("\(animated ? "AnimatedImage" : "WebImage")")
|
||||||
|
Spacer()
|
||||||
|
if animated {
|
||||||
|
AnimatedImage(url: url)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 100, height: 100)
|
||||||
|
} else {
|
||||||
|
WebImage(url: url)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 100, height: 100)
|
||||||
|
}
|
||||||
|
Button("Toggle \(isOn ? "nil" : "valid") URL") {
|
||||||
|
isOn.toggle()
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
Toggle("Switch", isOn: $animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test Switching url using @State
|
// Test Switching url using @State
|
||||||
struct ContentView2: View {
|
struct ContentView2: View {
|
||||||
@State var imageURLs = [
|
@State var imageURLs = [
|
||||||
|
|
|
@ -27,6 +27,13 @@ public final class AnimatedImageCoordinator: NSObject {
|
||||||
/// Data Binding Object, only properties in this object can support changes from user with @State and refresh
|
/// 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, *)
|
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
|
||||||
final class AnimatedImageModel : ObservableObject {
|
final class AnimatedImageModel : ObservableObject {
|
||||||
|
enum Kind {
|
||||||
|
case url
|
||||||
|
case data
|
||||||
|
case name
|
||||||
|
case unknown
|
||||||
|
}
|
||||||
|
var kind: Kind = .unknown
|
||||||
/// URL image
|
/// URL image
|
||||||
@Published var url: URL?
|
@Published var url: URL?
|
||||||
@Published var webOptions: SDWebImageOptions = []
|
@Published var webOptions: SDWebImageOptions = []
|
||||||
|
@ -123,6 +130,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
|
||||||
/// - Parameter isAnimating: The binding for animation control
|
/// - 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) {
|
public init(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding<Bool> = .constant(true), placeholderImage: PlatformImage? = nil) {
|
||||||
let imageModel = AnimatedImageModel()
|
let imageModel = AnimatedImageModel()
|
||||||
|
imageModel.kind = .url
|
||||||
imageModel.url = url
|
imageModel.url = url
|
||||||
imageModel.webOptions = options
|
imageModel.webOptions = options
|
||||||
imageModel.webContext = context
|
imageModel.webContext = context
|
||||||
|
@ -138,6 +146,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
|
||||||
/// - Parameter isAnimating: The binding for animation control
|
/// - Parameter isAnimating: The binding for animation control
|
||||||
public init<T>(url: URL?, options: SDWebImageOptions = [], context: [SDWebImageContextOption : Any]? = nil, isAnimating: Binding<Bool> = .constant(true), @ViewBuilder placeholder: @escaping () -> T) where T : View {
|
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()
|
let imageModel = AnimatedImageModel()
|
||||||
|
imageModel.kind = .url
|
||||||
imageModel.url = url
|
imageModel.url = url
|
||||||
imageModel.webOptions = options
|
imageModel.webOptions = options
|
||||||
imageModel.webContext = context
|
imageModel.webContext = context
|
||||||
|
@ -157,6 +166,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
|
||||||
/// - Parameter isAnimating: The binding for animation control
|
/// - Parameter isAnimating: The binding for animation control
|
||||||
public init(name: String, bundle: Bundle? = nil, isAnimating: Binding<Bool> = .constant(true)) {
|
public init(name: String, bundle: Bundle? = nil, isAnimating: Binding<Bool> = .constant(true)) {
|
||||||
let imageModel = AnimatedImageModel()
|
let imageModel = AnimatedImageModel()
|
||||||
|
imageModel.kind = .name
|
||||||
imageModel.name = name
|
imageModel.name = name
|
||||||
imageModel.bundle = bundle
|
imageModel.bundle = bundle
|
||||||
self.init(imageModel: imageModel, isAnimating: isAnimating)
|
self.init(imageModel: imageModel, isAnimating: isAnimating)
|
||||||
|
@ -168,6 +178,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
|
||||||
/// - Parameter isAnimating: The binding for animation control
|
/// - Parameter isAnimating: The binding for animation control
|
||||||
public init(data: Data, scale: CGFloat = 1, isAnimating: Binding<Bool> = .constant(true)) {
|
public init(data: Data, scale: CGFloat = 1, isAnimating: Binding<Bool> = .constant(true)) {
|
||||||
let imageModel = AnimatedImageModel()
|
let imageModel = AnimatedImageModel()
|
||||||
|
imageModel.kind = .data
|
||||||
imageModel.data = data
|
imageModel.data = data
|
||||||
imageModel.scale = scale
|
imageModel.scale = scale
|
||||||
self.init(imageModel: imageModel, isAnimating: isAnimating)
|
self.init(imageModel: imageModel, isAnimating: isAnimating)
|
||||||
|
@ -275,57 +286,72 @@ public struct AnimatedImage : PlatformViewRepresentable {
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateViewForName(_ name: String, view: AnimatedImageViewWrapper, context: Context) {
|
||||||
|
var image: PlatformImage?
|
||||||
|
#if os(macOS)
|
||||||
|
image = SDAnimatedImage(named: name, in: imageModel.bundle)
|
||||||
|
if image == nil {
|
||||||
|
// For static image, use NSImage as defaults
|
||||||
|
let bundle = imageModel.bundle ?? .main
|
||||||
|
image = bundle.image(forResource: name)
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
image = SDAnimatedImage(named: name, in: imageModel.bundle, compatibleWith: nil)
|
||||||
|
if image == nil {
|
||||||
|
// For static image, use UIImage as defaults
|
||||||
|
image = PlatformImage(named: name, in: imageModel.bundle, compatibleWith: nil)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
context.coordinator.imageLoading.imageName = name
|
||||||
|
view.wrapped.image = image
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateViewForData(_ data: Data, view: AnimatedImageViewWrapper, context: Context) {
|
||||||
|
var image: PlatformImage? = SDAnimatedImage(data: data, scale: imageModel.scale)
|
||||||
|
if image == nil {
|
||||||
|
// For static image, use UIImage as defaults
|
||||||
|
image = PlatformImage.sd_image(with: data, scale: imageModel.scale)
|
||||||
|
}
|
||||||
|
context.coordinator.imageLoading.imageData = data
|
||||||
|
view.wrapped.image = image
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateViewForURL(_ url: URL?, view: AnimatedImageViewWrapper, context: Context) {
|
||||||
|
// Determine if image already been loaded and URL is match
|
||||||
|
var shouldLoad: Bool
|
||||||
|
if url != context.coordinator.imageLoading.imageURL {
|
||||||
|
// Change the URL, need new loading
|
||||||
|
shouldLoad = true
|
||||||
|
context.coordinator.imageLoading.imageURL = url
|
||||||
|
} else {
|
||||||
|
// Same URL, check if already loaded
|
||||||
|
if context.coordinator.imageLoading.isLoading {
|
||||||
|
shouldLoad = false
|
||||||
|
} else if let image = context.coordinator.imageLoading.image {
|
||||||
|
shouldLoad = false
|
||||||
|
view.wrapped.image = image
|
||||||
|
} else {
|
||||||
|
shouldLoad = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if shouldLoad {
|
||||||
|
setupIndicator(view, context: context)
|
||||||
|
loadImage(view, context: context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateView(_ view: AnimatedImageViewWrapper, context: Context) {
|
func updateView(_ view: AnimatedImageViewWrapper, context: Context) {
|
||||||
// Refresh image, imageModel is the Source of Truth, switch the type
|
// Refresh image, imageModel is the Source of Truth, switch the type
|
||||||
// Although we have Source of Truth, we can check the previous value, to avoid re-generate SDAnimatedImage, which is performance-cost.
|
// Although we have Source of Truth, we can check the previous value, to avoid re-generate SDAnimatedImage, which is performance-cost.
|
||||||
if let name = imageModel.name, name != context.coordinator.imageLoading.imageName {
|
let kind = imageModel.kind
|
||||||
var image: PlatformImage?
|
if kind == .name, let name = imageModel.name, name != context.coordinator.imageLoading.imageName {
|
||||||
#if os(macOS)
|
updateViewForName(name, view: view, context: context)
|
||||||
image = SDAnimatedImage(named: name, in: imageModel.bundle)
|
} else if kind == .data, let data = imageModel.data, data != context.coordinator.imageLoading.imageData {
|
||||||
if image == nil {
|
updateViewForData(data, view: view, context: context)
|
||||||
// For static image, use NSImage as defaults
|
} else if kind == .url {
|
||||||
let bundle = imageModel.bundle ?? .main
|
updateViewForURL(imageModel.url, view: view, context: context)
|
||||||
image = bundle.image(forResource: name)
|
} else {
|
||||||
}
|
fatalError("Unsupported model kind: \(kind)")
|
||||||
#else
|
|
||||||
image = SDAnimatedImage(named: name, in: imageModel.bundle, compatibleWith: nil)
|
|
||||||
if image == nil {
|
|
||||||
// For static image, use UIImage as defaults
|
|
||||||
image = PlatformImage(named: name, in: imageModel.bundle, compatibleWith: nil)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
context.coordinator.imageLoading.imageName = name
|
|
||||||
view.wrapped.image = image
|
|
||||||
} else if let data = imageModel.data, data != context.coordinator.imageLoading.imageData {
|
|
||||||
var image: PlatformImage? = SDAnimatedImage(data: data, scale: imageModel.scale)
|
|
||||||
if image == nil {
|
|
||||||
// For static image, use UIImage as defaults
|
|
||||||
image = PlatformImage.sd_image(with: data, scale: imageModel.scale)
|
|
||||||
}
|
|
||||||
context.coordinator.imageLoading.imageData = data
|
|
||||||
view.wrapped.image = image
|
|
||||||
} else if let url = imageModel.url {
|
|
||||||
// Determine if image already been loaded and URL is match
|
|
||||||
var shouldLoad: Bool
|
|
||||||
if url != context.coordinator.imageLoading.imageURL {
|
|
||||||
// Change the URL, need new loading
|
|
||||||
shouldLoad = true
|
|
||||||
context.coordinator.imageLoading.imageURL = url
|
|
||||||
} else {
|
|
||||||
// Same URL, check if already loaded
|
|
||||||
if context.coordinator.imageLoading.isLoading {
|
|
||||||
shouldLoad = false
|
|
||||||
} else if let image = context.coordinator.imageLoading.image {
|
|
||||||
shouldLoad = false
|
|
||||||
view.wrapped.image = image
|
|
||||||
} else {
|
|
||||||
shouldLoad = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if shouldLoad {
|
|
||||||
setupIndicator(view, context: context)
|
|
||||||
loadImage(view, context: context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
|
|
|
@ -163,6 +163,7 @@ public struct WebImage<Content> : View where Content: View {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
content((imageManager.error != nil) ? .failure(imageManager.error!) : .empty)
|
content((imageManager.error != nil) ? .failure(imageManager.error!) : .empty)
|
||||||
|
setupPlaceholder()
|
||||||
// Load Logic
|
// Load Logic
|
||||||
.onPlatformAppear(appear: {
|
.onPlatformAppear(appear: {
|
||||||
self.setupManager()
|
self.setupManager()
|
||||||
|
@ -326,6 +327,14 @@ public struct WebImage<Content> : View where Content: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Placeholder View Support
|
||||||
|
func setupPlaceholder() -> some View {
|
||||||
|
let result = content((imageManager.error != nil) ? .failure(imageManager.error!) : .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
|
// Layout
|
||||||
|
|
Loading…
Reference in New Issue