93 lines
2.5 KiB
Swift
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 }
|
|
}
|