'How can I add caching to AsyncImage
I'm new to SwiftUI and was looking how to download images from a URL. I've found out that in iOS15 you can use AsyncImage to handle all the phases of an Image. The code looks like this.
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
}
}
I would launch my app and every time I would scroll up & down on my List, it would download the images again. So how would I be able to add a cache. I was trying to add a cache the way I did in Swift. Something like this.
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()
}
}
But it didn't go to good. So I was wondering is there a way to add caching to AsyncImage? Thanks would appreciate any help.
Solution 1:[1]
I had the same problem as you. I solved it by writing a CachedAsyncImage that kept the same API as AsyncImage, so that they could be interchanged easily, also in view of future native cache support in AsyncImage.
I made a Swift Package to share it.
CachedAsyncImage has the exact same API and behavior as AsyncImage, so you just have to change this:
AsyncImage(url: logoURL)
to this:
CachedAsyncImage(url: logoURL)
In addition to AsyncImage initializers, you have the possibilities to specify the cash you want to use (by default URLCache.shared is used):
CachedAsyncImage(url: logoURL, urlCache: .imageCache)
// URLCache+imageCache.swift
extension URLCache {
static let imageCache = URLCache(memoryCapacity: 512*1000*1000, diskCapacity: 10*1000*1000*1000)
}
Remember when setting the cache the response (in this case our image) must be no larger than about 5% of the disk cache (See this discussion).
Here is the repo.
Solution 2:[2]
Hope this can help others. I found this great video which talks about using the code below to build a async image cache function for your own use.
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
}
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Lorenzo Fiamingo |
| Solution 2 | Ryan Fung |
