GarrethTrent
GarrethTrent

Reputation: 35

How to load images from url into dictionary as data using swift 3.1

I am very new to swift and need some help with fetching images from URLs and storing them into a dictionary to reference into a UITableView. I've checked out the various threads, but can't find a scenario which meets by specific need.

I currently have the names of products in a dictionary as a key with the image URLs linked to each name:

let productLibrary = ["Product name 1":"http://www.website.com/image1.jpg", 
"Product name 2":"http://www.website.com/image2.jpg"]

I would need to get the actual images into a dictionary with the same product name as a key to add to the UITableView.

I currently have the images loading directly in the tableView cellForRowAt function, using the following code, but this makes the table view unresponsive due to it loading the images each time the TableView refreshes:

cell.imageView?.image = UIImage(data: try! Data(contentsOf: URL(string:
productLibrary[mainPlaces[indexPath.row]]!)!))

mainPlaces is an array of a selection of the products listed in the productLibrary dictionary. Loading the images initially up-front in a dictionary would surely decrease load time and make the UITableView as responsive as I need it to be.

Any assistance would be greatly appreciated!

@Samarth, I have implemented your code as suggested below (just copied the extension straight into the root of the ViewController.swift file above class ViewController.

The rest, I have pasted below the class ViewController class as below, but it's still not actually displaying the images in the tableview.

I've tried to do exactly as you've advised, but perhaps I'm missing something obvious. Sorry for the many responses but I just can't seem to get it working. Please see my exact code below:

internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")

    cell.textLabel?.text = mainPlaces[indexPath.row]

    downloadImage(url: URL(string: productLibrary[mainPlaces[indexPath.row]]!)!)

    cell.imageView?.downloadedFrom(link: productLibrary[mainPlaces[indexPath.row]]!)

    return cell

}

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    performSegue(withIdentifier: "ProductSelect", sender: nil)

        globalURL = url[mainPlaces[indexPath.row]]!

}

func getDataFromUrl(url: URL, completion: @escaping (_ data: Data?, _  response: URLResponse?, _ error: Error?) -> Void) {
    URLSession.shared.dataTask(with: url) {
        (data, response, error) in
        completion(data, response, error)
        }.resume()
}

func downloadImage(url: URL) {
    print("Download Started")
    getDataFromUrl(url: url) { (data, response, error)  in
        guard let data = data, error == nil else { return }
        print(response?.suggestedFilename ?? url.lastPathComponent)
        print("Download Finished")
        DispatchQueue.main.async() { () -> Void in

            // self.imageView.image = UIImage(data: data)
            /* If you want to load the image in a table view cell then you have to define the table view cell over here and then set the image on that cell */
            // Define you table view cell over here and then write

            let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")

            cell.imageView?.image = UIImage(data: data)

        }
    }
}

Upvotes: 2

Views: 1363

Answers (2)

GarrethTrent
GarrethTrent

Reputation: 35

I managed to get this right using a dataTask method with a for loop in the viewDidLoad method, which then updated a global dictionary so it didn't have to repopulate when I switched viewControllers, and because it isn't in the tableView method, the table remains responsive while the images load. The url's remains stored as a dictionary linked to the products, and the dictionary then gets populated with the actual UIImage as a dictionary with the product name as the key. Code as follows:

if images.count == 0 {
    for product in productLibrary {

        let picurl = URL(string: product.value)

        let request = NSMutableURLRequest(url: picurl!)

        let task = URLSession.shared.dataTask(with: request as URLRequest) {
            data, response, error in

            if error != nil {

                print(error)

            } else {

                if let data = data {

                    if let tempImage = UIImage(data: data) {

                        images.updateValue(tempImage, forKey: product.key)

                    }

                }


            }

        }

        task.resume()


    }

    }

I hope this helps, as this is exactly what I was hoping to achieve when I asked this question and is simple to implement.

Thanks to everyone who contributed.

Upvotes: 0

Samarth Kejriwal
Samarth Kejriwal

Reputation: 1176

You can load the images synchronously or asynchronously in your project.

Synchronous: means that your data is being loaded on the main thread, so till the time your data is being loaded, your main thread (UI Thread) will be blocked. This is what is happening in your project

Asynchronous: means your data is being loaded on a different thread other than UI thread, so that UI is not being blocked and your data loading is done in the background.

Try this example to load the image asynchronously :

Asynchronously:

Create a method with a completion handler to get the image data from your url

func getDataFromUrl(url: URL, completion: @escaping (_ data: Data?, _  response: URLResponse?, _ error: Error?) -> Void) {
    URLSession.shared.dataTask(with: url) {
        (data, response, error) in
        completion(data, response, error)
    }.resume()
}

Create a method to download the image (start the task)

func downloadImage(url: URL) {
    print("Download Started")
    getDataFromUrl(url: url) { (data, response, error)  in
        guard let data = data, error == nil else { return }
        print(response?.suggestedFilename ?? url.lastPathComponent)
        print("Download Finished")
        DispatchQueue.main.async() { () -> Void in

           // self.imageView.image = UIImage(data: data)
   /* If you want to load the image in a table view cell then you have to define the table view cell over here and then set the image on that cell */
           // Define you table view cell over here and then write 
           //      cell.imageView?.image = UIImage(data: data)

        }
    }
}

Usage:

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    print("Begin of code")
    if let checkedUrl = URL(string: "your image url") {
        imageView.contentMode = .scaleAspectFit
        downloadImage(url: checkedUrl)
    }
    print("End of code. The image will continue downloading in the background and it will be loaded when it ends.")
}

Extension:

extension UIImageView {
    func downloadedFrom(url: URL, contentMode mode: UIViewContentMode = .scaleAspectFit) {
        contentMode = mode
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            guard
                let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
                let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
                let data = data, error == nil,
                let image = UIImage(data: data)
            else { return }
            DispatchQueue.main.async() { () -> Void in
                self.image = image
            }
        }.resume()
    }
    func downloadedFrom(link: String, contentMode mode: UIViewContentMode = .scaleAspectFit) {
        guard let url = URL(string: link) else { return }
        downloadedFrom(url: url, contentMode: mode)
    }
}

Usage:

imageView.downloadedFrom(link: "image url")

For your question

Do this while you are loading the images in your table view cell:

 cell.imageView?.downloadedFrom(link: productLibrary[mainPlaces[indexPath.row]]! )

Upvotes: 4

Related Questions