UIImage(data:) instead. The rest of the old snippet needs the same treatment: use URL(string:) and Data(contentsOf:), and download network images with URLSession instead of blocking the main thread.Why this happens
Swift imports many Objective-C factory methods as initializers. +[UIImage imageWithData:] becomes UIImage(data:), and the old selector spelling is explicitly marked unavailable, so the compiler stops you with 'imageWithData' is unavailable: use object construction 'UIImage(data:)'. Nothing is missing — the API was renamed by the importer.
The rest of the snippet has the same problem. NSURL.URLWithString(...) and NSData.dataWithContentsOfURL(_:options:error:) are Objective-C spellings that don’t exist in Swift. You write URL(string:) and Data(contentsOf:) instead.
There is a second trap behind the compile error. Data(contentsOf:) is synchronous and throwing: it downloads the whole resource on the calling thread. Calling it in viewDidLoad freezes the UI until the download finishes, which is why the accepted answers push you toward URLSession.
How to fix it
1. The direct translation (local files only)
If the URL points at a file on disk, or the data is already downloaded, the one-line translation is fine:
import UIKit
let url = URL(string: "https://example.com/ios8.png")!
do {
let data = try Data(contentsOf: url)
imageView.image = UIImage(data: data)
} catch {
print("Failed to load image: \(error)")
}
UIImage(data:) is a failable initializer, so the result is UIImage?. Assigning it to imageView.image is fine because that property is also optional. Do not use this version for a remote URL on the main thread.
2. Download asynchronously with URLSession
This is the correct replacement for the Objective-C snippet when the image lives on a server:
func downloadImage(from url: URL) {
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard
let data,
error == nil,
let http = response as? HTTPURLResponse,
http.statusCode == 200,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async {
self?.imageView.image = image
}
}.resume()
}
Three details matter. The task does nothing until you call resume(). The completion handler runs on a background queue, so the UI update must be dispatched to the main queue. And checking statusCode == 200 prevents you from feeding an HTML error page into UIImage(data:), which would just return nil.
You can also check the MIME type before decoding:
let mimeType = response?.mimeType ?? ""
guard mimeType.hasPrefix("image") else { return }
3. Use async/await instead of completion handlers
On current iOS versions, URLSession has an async API that removes the nesting:
@MainActor
func loadImage(from url: URL) async {
do {
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { return }
imageView.image = UIImage(data: data)
} catch {
print("Download failed: \(error)")
}
}
Because the method is marked @MainActor, the assignment to imageView.image is already on the main thread. In SwiftUI you would call it from .task { await loadImage(from: url) }, which also cancels the task when the view disappears.
4. SwiftUI: use AsyncImage
If you are not in UIKit at all, AsyncImage (iOS 15 and later) does the download and the state handling for you:
import SwiftUI
AsyncImage(url: URL(string: "https://example.com/ios8.png")) { phase in
switch phase {
case .success(let image):
image.resizable().scaledToFit()
case .failure:
Image(systemName: "photo")
case .empty:
ProgressView()
@unknown default:
EmptyView()
}
}
AsyncImage does not cache beyond whatever URLCache does, and it gives you no control over decoding size. For lists with many images, a dedicated image-loading library is usually a better fit.
5. Cache so you don’t download the same image twice
Every scroll through a list can fire the same request again. URLCache honours the server’s cache headers, and you can raise its limits at launch:
URLCache.shared = URLCache(
memoryCapacity: 50 * 1024 * 1024,
diskCapacity: 200 * 1024 * 1024
)
If you want memory caching, cancellation and downsampling out of the box, Kingfisher is the common choice:
import Kingfisher
imageView.kf.setImage(with: url)
KFImage(URL(string: "https://example.com/ios8.png")!)
6. Watch out for App Transport Security
A plain http:// URL will fail at runtime with Transport Security has blocked a cleartext HTTP resource load. Use https://. If you genuinely must hit an HTTP host during development, add a scoped exception in Info.plist rather than disabling ATS globally.
How to prevent it
- Translate Objective-C factory methods to Swift initializers:
UIImage(data:),URL(string:),Data(contentsOf:). - Never call
Data(contentsOf:)on the main thread for a remote URL. - Always update
UIImageViewfrom the main queue or from a@MainActorcontext. - Validate the HTTP status code and MIME type before handing bytes to
UIImage. - Serve images over HTTPS so ATS doesn’t reject the request.
- Cache decoded images; re-downloading on every cell reuse is the usual cause of janky lists.
FAQ
Why does UIImage(data:) return an optional?
Because the data may not be a valid image. If the server returns an error page or a truncated file, the initializer returns nil. Unwrap it with guard let or if let instead of force-unwrapping.
Can I just use Data(contentsOf:) for a network image?
It compiles and it works, but it downloads synchronously on the calling thread. On the main thread that means a frozen UI for the length of the request. Use URLSession or the async data(from:) method instead.
Is imageWithData: deprecated?
No. It still exists in Objective-C. The Swift importer renames it to UIImage(data:) and marks the old spelling unavailable, which is why only Swift code fails to compile.
Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.
