Yestay Muratov
Yestay Muratov

Reputation: 1370

public function that loads image async and returns UIImage

I am trying to write public function that loads image async and returns UIImage. I want to use it in filling UITableView with images.

class CommonFunctions {

func loadImageAsync(StringURL: NSString) -> UIImage{

    var returningImage = UIImage()

    let url = NSURL(string: StringURL)

    let requestedURL = NSURLRequest(URL: url!)

    NSURLConnection.sendAsynchronousRequest(requestedURL, queue: NSOperationQueue.mainQueue(), completionHandler: {
        response, data, error in

        if error != nil {
            println("there is some error loading image")
        }
        else {

            if let image = UIImage(data: data){
                returningImage = image
            }
        }
    })
    return returningImage
    }

}

the problem is that when I want to use this function :

cell.imageModelImage.image = CommonFunctions.loadImageAsync(CommonFunctions)

Instead of String argument I get the class? Why is that so?

Upvotes: 1

Views: 2179

Answers (1)

Rob
Rob

Reputation: 437552

You ask:

Instead of String argument I get the class? Why is that so?

It is because you are calling it as if it was a class function, but didn't define it as such. If you add the class keyword on the declaration of the function, you won't see that behavior.

But there are deeper issues here: You cannot return a value from an asynchronous function (because the function will return immediately while the asynchronous request will not complete until later).

One solution is to provide a completion handler that will be called if the image is retrieved successfully:

class func loadImageAsync(stringURL: String, completion: @escaping (UIImage?, Error?) -> Void) {
    let url = URL(string: stringURL)!

    URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data, error == nil else {
            DispatchQueue.main.async { completion(nil, error) }
            return
        }
        let image = UIImage(data: data)
        DispatchQueue.main.async { completion(image, nil) }
    }.resume()
}

Note, I'm dispatching the completion handler back to the main queue, where all UI updates should take place.

Then, in tableView(_:cellForRowAt:), you can supply a block that will be called when the asynchronous request is done:

cell.imageModelImage.image = ... // remember to initialize image view before starting async method
CommonFunctions.loadImageAsync(with: "http://example.com/test.jpg") { image, error in
    if let cell = tableView.cellForRow(at: indexPath) as? CustomCell {  // make sure cell hasn't scrolled out of view
        cell.imageModelImage.image = image
    }
}

Note, the above assumes that it's impossible to insert a row in the table in the intervening period of time. If that assumption is not valid, rather than using the old indexPath, you might have requery your model to identify what IndexPath is valid for this cell when the asynchronous completion handler is called.

For Swift 2 rendition, see the previous revision of this answer.

Upvotes: 2

Related Questions