Skip to main content

Command Palette

Search for a command to run...

Image Caching in SwiftUI

Updated
3 min read
Image Caching in SwiftUI
X

iOS developer from Toronto, ON, CAN

Caching images is an important technique for optimizing the performance of your SwiftUI app. When you load an image, it can take time to fetch it from a remote server or read it from disk. By caching the image, you can avoid repeating this time-consuming process every time the image is needed. In this blog post, we'll explore how to cache images in SwiftUI.

The code in this post is available here.

Using Image Cache

One way to cache images in SwiftUI is to create an ImageCache class. This class can store a cache of UIImage objects, and it can be accessed from any part of your app. Here's an example of how you can create an ImageCache class:

class ImageCache {
    static let shared = ImageCache()

    private let cache = NSCache<NSString, UIImage>()

    private init() {}

    func set(_ image: UIImage, forKey key: String) {
        cache.setObject(image, forKey: key as NSString)
    }

    func get(forKey key: String) -> UIImage? {
        return cache.object(forKey: key as NSString)
    }
}

In this example, we've created a singleton ImageCache object using the shared property. The cache property is an NSCache object that stores UIImage objects, and we've defined set(_:,forKey:) and get(forKey:) methods to add and retrieve images from the cache.

Caching Images with URL

Another common use case for caching images in SwiftUI is when loading images from a remote server. In this case, you can use the dataTask(with:completionHandler:) method of the URLSession API to load the image data asynchronously. Once the image data is loaded, you can cache it using the ImageCache class.

struct RemoteImage: View {
    @ObservedObject var imageLoader: ImageLoader

    init(url: String) {
        imageLoader = ImageLoader(url: url)
    }

    var body: some View {
        if let image = imageLoader.image {
            Image(uiImage: image)
                .resizable()
        } else {
            ProgressView()
        }
    }
}

class ImageLoader: ObservableObject {
    @Published var image: UIImage?

    private var url: String
    private var task: URLSessionDataTask?

    init(url: String) {
        self.url = url
        loadImage()
    }

    private func loadImage() {
        if let cachedImage = ImageCache.shared.get(forKey: url) {
            self.image = cachedImage
            return
        }

        guard let url = URL(string: url) else { return }

        task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else { return }

            DispatchQueue.main.async {
                let image = UIImage(data: data)
                self.image = image
                ImageCache.shared.set(image!, forKey: self.url)
            }
        }
        task?.resume()
    }
}

In this example, we've created a RemoteImage view that loads an image from a remote server using the ImageLoader class. The ImageLoader class is an ObservableObject that uses the dataTask(with:completionHandler:) method to load the image data asynchronously. We've also added a check for cached images using the ImageCache class, which avoids fetching the image from the remote server if it's already cached.

Note: To validate the implementation above, you can use the image from the project iOSDevX with the URL: "https://github.com/xavier7t/iOSDevX/blob/main/iOSDevX/Assets.xcassets/demo.imageset/demo.png?raw=true". You can write a simple SwiftUI View like the one below:

struct ContentView: View {
    let urlString = "https://github.com/xavier7t/iOSDevX/blob/main/iOSDevX/Assets.xcassets/demo.imageset/demo.png?raw=true"
    var body: some View {
        VStack {
            RemoteImage(url: urlString)
                .frame(width: 150, height: 150, alignment: .center)
                .cornerRadius(10)
        }
    }
}

Conclusion

Caching images is an important technique for optimizing the performance of your SwiftUI app. By creating an ImageCache class and using it to cache images, you can avoid repeating time-consuming image-loading operations.

And that’s all of today’s post. I hope it helps and let me know if it is by leaving a comment. Don’t forget to subscribe to my newsletter if you’d like to receive posts like this via email.

I’ll see you in the next post!

V

Loved this implementation. Simple concept that is easy to integrate without having to add a lot of code.

J

Hey Xavier! Good stuff, thanks for the article.

I was wondering, how would you handle uploading a local file and having it immediately reflect in the UI? Because the Image views use instances of ImageLoader to load the image, which makes it hard to access those instances and change the image.

Let's say I have photosPicker, from where I get the image data from PhotosPickerItem for a newly uploaded image. But I can't wrap my head around how I'd get that new data into the ImageLoader instances that are already being used (and now with the old, outdated image) in my views.

A

I used fileImporter and got local URL to image then I am getting data for URL like the following

let _ = fileUrl.startAccessingSecurityScopedResource() let file = try Data(contentsOf: fileUrl) fileUrl.stopAccessingSecurityScopedResource()

then I am uploading the data to my server and getting URL and displaying that in my ImageView

if that helps