import SwiftUI final class ImageResource: ObservableObject { /// Globally unique id @Published var id: String @Published var altText: LocalizedText @Published var size: CGSize = .zero var aspectRatio: CGFloat { guard size.height > 0 else { return 0 } return size.width / size.height } private let source: ImageSource init(uniqueId: String, altText: LocalizedText, fileUrl: URL) { self.id = uniqueId self.source = .file(fileUrl) self.altText = altText } init(resourceName: String) { self.id = resourceName self.source = .resource(resourceName) self.altText = .init(en: "A test image included in the bundle", de: "Ein Test-Image aus dem Bundle") } private enum ImageSource { case file(URL) case resource(String) } } extension ImageResource: Identifiable { } extension ImageResource: Equatable { static func == (lhs: ImageResource, rhs: ImageResource) -> Bool { lhs.id == rhs.id } } extension ImageResource: Hashable { func hash(into hasher: inout Hasher) { hasher.combine(id) } } extension ImageResource { var imageToDisplay: Image { switch source { case .file(let url): return image(at: url) case .resource(let name): return .init(name) } } private func image(at url: URL) -> Image { let imageData: Data do { imageData = try Data(contentsOf: url) } catch { print("Failed to load image data from \(url.path): \(error)") return failureImage } guard let loadedImage = NSImage(data: imageData) else { print("Failed to create image from \(url.path)") return failureImage } if self.size == .zero && loadedImage.size != .zero { DispatchQueue.main.async { self.size = loadedImage.size } } return .init(nsImage: loadedImage) } private var failureImage: SwiftUI.Image { Image(systemSymbol: .exclamationmarkTriangle) } }