62 lines
2.0 KiB
Swift
62 lines
2.0 KiB
Swift
//
|
|
// AsyncImageWithPlaceholder.swift
|
|
// meterios
|
|
//
|
|
// (c) 2025 Martin "maride" Dessauer
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
/// **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, display a dummy image (question mark)
|
|
ZStack {
|
|
baseShapeView
|
|
Text("?")
|
|
.fontWeight(.bold)
|
|
.foregroundStyle(.gray)
|
|
}
|
|
} else {
|
|
// Loading Placeholder
|
|
ZStack {
|
|
baseShapeView
|
|
ProgressView()
|
|
}
|
|
}
|
|
}.fixedSize()
|
|
}
|
|
}
|