// // AsyncImageWithPlaceholder.swift // meterios // // (c) 2025 Martin "maride" Dessauer // import Foundation import SwiftUI import OSLog /// **AsyncImageWithPlaceholder** wraps AsyncImage, showing a Spinner while loading and a question mark in case of errors. struct AsyncImageWithPlaceholder : View { private var url: URL? private var width: CGFloat private var height: CGFloat // Access the current color scheme (that is, e.g.: dark mode, light mode) environment value @Environment(\.colorScheme) var colorScheme // baseShape and baseShapeView resemble the "background" (while loading, and in case of errors, and for the image itself) private var baseShape : some Shape = RoundedRectangle(cornerRadius: 8.0) var baseShapeView : some View { baseShape .foregroundStyle(colorScheme == .dark ? .black : .white) .frame(width: width, height: height) } // Creates a new instance of AsyncImageWithPlaceholder, loading the image pointed to by url, with exact measures of width by height init(url: URL? = nil, width: CGFloat, height: CGFloat) { self.url = url self.width = width self.height = height } var body: some View { AsyncImage(url: self.url) { phase in if let image = phase.image { // Image loaded image .resizable() .aspectRatio(contentMode: .fit) .frame(width: self.width, height: self.height) .clipShape(baseShape) } else if phase.error != nil { // Error, log and display a dummy image (question mark) let _ = Logger().error("Error loading image: \(phase.error)") ZStack { baseShapeView Text("?") .fontWeight(.bold) .foregroundStyle(.gray) } } else { // Loading Placeholder ZStack { baseShapeView ProgressView() } } }.fixedSize() } }