Introduction: Why is this question so popular?
\n\nAnyone who has ever written an iOS app has faced the task: "How do I display an image from the internet in my app?" On Stack Overflow, the question "Loading/Downloading image from URL on Swift" has garnered thousands of views and hundreds of answers. Why? Because it's a fundamental task that marks the beginning of working with networking in iOS. And although it seems simple — take a URL, download the data, display it — in practice, developers encounter many pitfalls: blocking the main thread, memory leaks, lack of caching, and slow performance.
\n\nIn this article, we will cover all popular ways to load images from a URL in Swift, answer frequently asked questions, and provide code examples that you can use immediately in your project.
\n\n1. The Simplest Way: URLSession + Data
\n\nLet's start with the classic approach. In Swift, URLSession is used to download data over the network. Here's what minimal working code looks like:
import UIKit\n\nfunc loadImage(from urlString: String, completion: @escaping (UIImage?) -> Void) {\n guard let url = URL(string: urlString) else {\n completion(nil)\n return\n }\n \n URLSession.shared.dataTask(with: url) { data, response, error in\n guard let data = data, error == nil else {\n completion(nil)\n return\n }\n let image = UIImage(data: data)\n DispatchQueue.main.async {\n completion(image)\n }\n }.resume()\n}\n\n\nImportant: we pass the result to the main thread using DispatchQueue.main.async. If you don't do this, you risk a crash when trying to update the UI from a background thread.
How to use:
\n\nloadImage(from: "https://example.com/image.jpg") { image in\n self.imageView.image = image\n}\n\n\nThis method is suitable for simple projects where you need to load 1-2 images. But for real-world applications, it's not optimal: there is no caching, no error handling, and scrolling through a table will be sluggish.
\n\n2. Modern Approach: async/await (Swift 5.5+)
\n\nStarting with iOS 15 and Swift 5.5, it became possible to write asynchronous code in an imperative style using async/await. Loading an image becomes elegant and readable:
import UIKit\n\nfunc loadImageAsync(from urlString: String) async throws -> UIImage? {\n guard let url = URL(string: urlString) else {\n throw URLError(.badURL)\n }\n \n let (data, _) = try await URLSession.shared.data(from: url)\n return UIImage(data: data)\n}\n\n\nUsage in SwiftUI:
\n\nstruct ContentView: View {\n @State private var image: UIImage?\n \n var body: some View {\n VStack {\n if let image = image {\n Image(uiImage: image)\n .resizable()\n .scaledToFit()\n } else {\n ProgressView()\n }\n }\n .task {\n do {\n image = try await loadImageAsync(from: "https://example.com/image.jpg")\n } catch {\n print("Loading error: \\(error)")\n }\n }\n }\n}\n\n\nWhy is this cool? The code becomes linear, without nested closures, and is easy to read. However, the caching problem remains — we still download the image from scratch every time.
\n\n3. Caching: How to Save the User's Data
\n\nThe most common question beginners ask is: "Why do my images load every time I scroll the table?" The answer is simple — you are not using a cache.
\n\niOS has a built-in URLCache that can be configured for URLSession. But for images, it's better to use NSCache — it automatically