I'm trying to use an extension of UIImage to convert image download URLs to UIImage, but I'm getting this error: 'self.init' isn't called on all paths before returning from initializer
Here is my whole extension:
(https://i.stack.imgur.com/oZGJu.png)
extension UIImage {
convenience init?(url: URL?) {
let session = URLSession(configuration: .default)
let downloadPicTask = session.dataTask(with: url!) { (data, response, error) in
if let e = error {
print("Error downloading picture: \(e)")
} else {
if let res = response as? HTTPURLResponse {
print("Downloaded picture with response code \(res.statusCode)")
if let imageData = data {
let image = UIImage(data: imageData)
} else {
print("Couldn't get image: Image is nil")
}
} else {
print("Couldn't get response code for some reason")
}
}
}
downloadPicTask.resume()
}
}
I don't know what I'm missing or if I should try a new way to convert URL to UIImage. I tried a different way, but it throws an error saying I need to do it asynchronously.
CodePudding user response:
A standard initializer cannot simply return an image that was fetched asynchronously.
Assuming you are not using Swift concurrency with its async
-await
, you can write a static
function with a completion handler closure which will asynchronously return the image:
extension UIImage {
enum ImageError: Error {
case notImage
case unknownError
}
@discardableResult
static func fetchImage(from url: URL, queue: DispatchQueue = .main, completion: @escaping (Result<UIImage, Error>) -> Void) -> URLSessionDataTask? {
let session = URLSession.shared
let task = session.dataTask(with: url) { data, response, error in
guard
let data = data,
error == nil,
let response = response as? HTTPURLResponse
else {
queue.async { completion(.failure(error ?? ImageError.unknownError)) }
return
}
print("Downloaded picture with response code \(response.statusCode)")
guard let image = UIImage(data: data) else {
queue.async { completion(.failure(ImageError.notImage)) }
return
}
queue.async { completion(.success(image)) }
}
task.resume()
return task
}
}
And then call this static
function with its completion handler, e.g.:
func updateImageView() {
let url = ...
UIImage.fetchImage(from: url) { [weak self] result in
switch result {
case .failure(let error): print(error)
case .success(let image): self?.imageView.image = image
}
}
}
However, if you were using Swift concurrency with its async
-await
, you can write an async
initializer:
extension UIImage {
convenience init?(from url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url)
self.init(data: data)
}
}
And you can then use this from an asynchronous context, e.g.:
func updateImageView() async throws {
let url = ...
imageView.image = try await UIImage(from: url)
}
CodePudding user response:
You’re missing a self.init call For convenience inits you’d write self.init and follow up with a standard init that’s already defined Also you need to return nil where your init fails