Files
ddys-client/DDYSClient/Views/Common/CachedAsyncImage.swift
2026-02-26 22:15:35 +08:00

93 lines
2.5 KiB
Swift

import SwiftUI
struct CachedAsyncImage<Placeholder: View>: View {
let url: URL?
@ViewBuilder let placeholder: () -> Placeholder
@State private var image: Image?
@State private var isLoading = false
var body: some View {
Group {
if let image {
image
.resizable()
.aspectRatio(contentMode: .fill)
} else {
placeholder()
.task(id: url) {
await loadImage()
}
}
}
}
private func loadImage() async {
guard let url, !isLoading else { return }
//
if let cached = ImageCache.shared.get(url) {
self.image = cached
return
}
isLoading = true
defer { isLoading = false }
do {
let (data, _) = try await ImageCache.shared.session.data(from: url)
#if os(macOS)
if let nsImage = NSImage(data: data) {
let img = Image(nsImage: nsImage)
ImageCache.shared.set(img, for: url)
self.image = img
}
#else
if let uiImage = UIImage(data: data) {
let img = Image(uiImage: uiImage)
ImageCache.shared.set(img, for: url)
self.image = img
}
#endif
} catch {
// placeholder
}
}
}
// MARK: -
private final class ImageCache: @unchecked Sendable {
static let shared = ImageCache()
private let cache = NSCache<NSURL, CacheEntry>()
let session: URLSession
private init() {
cache.countLimit = 200
cache.totalCostLimit = 100 * 1024 * 1024 // 100MB
//
let config = URLSessionConfiguration.default
config.urlCache = URLCache(
memoryCapacity: 50 * 1024 * 1024, // 50MB
diskCapacity: 200 * 1024 * 1024 // 200MB
)
config.requestCachePolicy = .returnCacheDataElseLoad
session = URLSession(configuration: config)
}
func get(_ url: URL) -> Image? {
cache.object(forKey: url as NSURL)?.image
}
func set(_ image: Image, for url: URL) {
cache.setObject(CacheEntry(image: image), forKey: url as NSURL)
}
}
private final class CacheEntry {
let image: Image
init(image: Image) { self.image = image }
}