Soy nuevo en SwiftUI y estaba buscando cómo descargar imágenes desde una URL. Descubrí que en iOS15 puedes usar AsyncImage para manejar todas las fases de una imagen. El código se ve así.
AsyncImage(url: URL(string: urlString)) { phase in switch phase { case .success(let image): image .someModifers case .empty: Image(systemName: "Placeholder Image") .someModifers case .failure(_): Image(systemName: "Error Image") .someModifers @unknown default: Image(systemName: "Placeholder Image") .someModifers } }Ejecutaría mi aplicación y cada vez que me desplazaría hacia arriba y hacia abajo en mi Lista, descargaría las imágenes nuevamente. Entonces, ¿cómo podría agregar un caché? Estaba tratando de agregar un caché como lo hice en Swift. Algo como esto.
struct DummyStruct { var imageCache = NSCache<NSString, UIImage>() func downloadImageFromURLString(_ urlString: String) { guard let url = URL(string: urlString) else { return } URLSession.shared.dataTask(with: url) { data, response, error in if let _ = error { fatalError() } guard let data = data, let image = UIImage(data: data) else { return } imageCache.setObject(image, forKey: NSString(string: urlString)) } .resume() } }Pero no salió bien. Entonces, me preguntaba si hay alguna manera de agregar el almacenamiento en caché a AsyncImage. Gracias agradecería cualquier ayuda.
Espero que esto pueda ayudar a otros. Encontré este excelente video que habla sobre el uso del código a continuación para crear una función de caché de imagen asíncrona para su propio uso.
import SwiftUI struct CacheAsyncImage<Content>: View where Content: View{ private let url: URL private let scale: CGFloat private let transaction: Transaction private let content: (AsyncImagePhase) -> Content init( url: URL, scale: CGFloat = 1.0, transaction: Transaction = Transaction(), @ViewBuilder content: @escaping (AsyncImagePhase) -> Content ){ self.url = url self.scale = scale self.transaction = transaction self.content = content } var body: some View{ if let cached = ImageCache[url]{ let _ = print("cached: \(url.absoluteString)") content(.success(cached)) }else{ let _ = print("request: \(url.absoluteString)") AsyncImage( url: url, scale: scale, transaction: transaction ){phase in cacheAndRender(phase: phase) } } } func cacheAndRender(phase: AsyncImagePhase) -> some View{ if case .success (let image) = phase { ImageCache[url] = image } return content(phase) } } fileprivate class ImageCache{ static private var cache: [URL: Image] = [:] static subscript(url: URL) -> Image?{ get{ ImageCache.cache[url] } set{ ImageCache.cache[url] = newValue } } }Yo tenía el mismo problema que tú. Lo resolví escribiendo un CachedAsyncImage que mantuvo la misma API que AsyncImage , para que pudieran intercambiarse fácilmente, también en vista del futuro soporte de caché nativo en AsyncImage .
Hice un Paquete Swift para compartirlo.
CachedAsyncImage tiene exactamente la misma API y comportamiento que AsyncImage , por lo que solo tiene que cambiar esto:
AsyncImage(url: logoURL)a esto:
CachedAsyncImage(url: logoURL) Además de los inicializadores AsyncImage , tiene la posibilidad de especificar el efectivo que desea usar (por defecto se usa URLCache.shared ):
CachedAsyncImage(url: logoURL, urlCache: .imageCache) // URLCache+imageCache.swift extension URLCache { static let imageCache = URLCache(memoryCapacity: 512*1000*1000, diskCapacity: 10*1000*1000*1000) }Recuerde que al configurar la memoria caché, la respuesta (en este caso, nuestra imagen) no debe ser superior al 5% de la memoria caché del disco (consulte esta discusión ).
Aquí está el repositorio .