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:
DreamPiggy 2024-03-18 17:43:28 +08:00 committed by GitHub
commit 54199185d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 120 additions and 48 deletions

View File

@ -17,6 +17,43 @@ class UserSettings: ObservableObject {
#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
struct ContentView2: View {
@State var imageURLs = [

View File

@ -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
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
final class AnimatedImageModel : ObservableObject {
enum Kind {
case url
case data
case name
case unknown
}
var kind: Kind = .unknown
/// URL image
@Published var url: URL?
@Published var webOptions: SDWebImageOptions = []
@ -123,6 +130,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
/// - 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.kind = .url
imageModel.url = url
imageModel.webOptions = options
imageModel.webContext = context
@ -138,6 +146,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
/// - 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 {
let imageModel = AnimatedImageModel()
imageModel.kind = .url
imageModel.url = url
imageModel.webOptions = options
imageModel.webContext = context
@ -157,6 +166,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
/// - Parameter isAnimating: The binding for animation control
public init(name: String, bundle: Bundle? = nil, isAnimating: Binding<Bool> = .constant(true)) {
let imageModel = AnimatedImageModel()
imageModel.kind = .name
imageModel.name = name
imageModel.bundle = bundle
self.init(imageModel: imageModel, isAnimating: isAnimating)
@ -168,6 +178,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
/// - Parameter isAnimating: The binding for animation control
public init(data: Data, scale: CGFloat = 1, isAnimating: Binding<Bool> = .constant(true)) {
let imageModel = AnimatedImageModel()
imageModel.kind = .data
imageModel.data = data
imageModel.scale = scale
self.init(imageModel: imageModel, isAnimating: isAnimating)
@ -275,10 +286,7 @@ public struct AnimatedImage : PlatformViewRepresentable {
return view
}
func updateView(_ view: AnimatedImageViewWrapper, context: Context) {
// 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.
if let name = imageModel.name, name != context.coordinator.imageLoading.imageName {
private func updateViewForName(_ name: String, view: AnimatedImageViewWrapper, context: Context) {
var image: PlatformImage?
#if os(macOS)
image = SDAnimatedImage(named: name, in: imageModel.bundle)
@ -296,7 +304,9 @@ public struct AnimatedImage : PlatformViewRepresentable {
#endif
context.coordinator.imageLoading.imageName = name
view.wrapped.image = image
} else if let data = imageModel.data, data != context.coordinator.imageLoading.imageData {
}
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
@ -304,7 +314,9 @@ public struct AnimatedImage : PlatformViewRepresentable {
}
context.coordinator.imageLoading.imageData = data
view.wrapped.image = image
} else if let url = imageModel.url {
}
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 {
@ -328,6 +340,20 @@ public struct AnimatedImage : PlatformViewRepresentable {
}
}
func updateView(_ view: AnimatedImageViewWrapper, context: Context) {
// 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.
let kind = imageModel.kind
if kind == .name, let name = imageModel.name, name != context.coordinator.imageLoading.imageName {
updateViewForName(name, view: view, context: context)
} else if kind == .data, let data = imageModel.data, data != context.coordinator.imageLoading.imageData {
updateViewForData(data, view: view, context: context)
} else if kind == .url {
updateViewForURL(imageModel.url, view: view, context: context)
} else {
fatalError("Unsupported model kind: \(kind)")
}
#if os(macOS)
if self.isAnimating != view.wrapped.animates {
view.wrapped.animates = self.isAnimating

View File

@ -163,6 +163,7 @@ public struct WebImage<Content> : View where Content: View {
}
} else {
content((imageManager.error != nil) ? .failure(imageManager.error!) : .empty)
setupPlaceholder()
// Load Logic
.onPlatformAppear(appear: {
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