24

I want to use lazy loading concept for my table view using swift. In my table view i am showing multiple cells which contain product images and product name . Please help me to get the solution.

Dharmesh Kheni
  • 71,228
  • 33
  • 160
  • 165
Vikram Pote
  • 5,433
  • 4
  • 33
  • 37
  • Duplicates a lot of questions. For instance http://stackoverflow.com/questions/16663618/async-image-loading-from-url-inside-a-uitableview-cell-image-changes-to-wrong – kean Nov 11 '15 at 13:33
  • 1
    that one is with objc this is for swift, so still useful – mikey Aug 14 '19 at 06:45

3 Answers3

42

Old Solution:

Since you doesn't show any code.

Here is the example for you.

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    // try to reuse cell
    let cell:CustomCell = tableView.dequeueReusableCellWithIdentifier("DealCell") as CustomCell

    // get the deal image
    let currentImage = deals[indexPath.row].imageID
    let unwrappedImage = currentImage
    var image = self.imageCache[unwrappedImage]
    let imageUrl = NSURL(string: "http://staging.api.cheapeat.com.au/deals/\(unwrappedImage)/photo")

    // reset reused cell image to placeholder
    cell.dealImage.image = UIImage(named: "placeholder")

    // async image
    if image == nil {

    let request: NSURLRequest = NSURLRequest(URL: imageUrl!)

    NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
        if error == nil {

            image = UIImage(data: data)

            self.imageCache[unwrappedImage] = image
            dispatch_async(dispatch_get_main_queue(), {
                cell.dealImage.image = image

            })
        }
        else {

        }
    })
    }

    else{
        cell.dealImage.image = image
    }

  return cell

}

Follow THIS tutorial for more Info. Hope this will help you.

New Solution:

Here is extension for it which is created by my friend Leo Dabus which is really simple to use:

extension UIImageView {
    func downloadImageFrom(link link:String, contentMode: UIViewContentMode) {
        NSURLSession.sharedSession().dataTaskWithURL( NSURL(string:link)!, completionHandler: {
            (data, response, error) -> Void in
            dispatch_async(dispatch_get_main_queue()) {
                self.contentMode =  contentMode
                if let data = data { self.image = UIImage(data: data) }
            }
        }).resume()
    }
}

Now in your cellForRowAtIndexPath method assign image to cell this way:

cell.cellImageView.image = UIImage(named: "placeholder")  //set placeholder image first.
cell.cellImageView.downloadImageFrom(link: imageLinkArray[indexPath.row], contentMode: UIViewContentMode.ScaleAspectFit)  //set your image from link array.

And as Rob suggested into comment here is some useful libraries which you can use:

  1. https://github.com/Alamofire/AlamofireImage
  2. https://github.com/onevcat/Kingfisher
  3. https://github.com/rs/SDWebImage
  4. https://github.com/kean/DFImageManager
Community
  • 1
  • 1
Dharmesh Kheni
  • 71,228
  • 33
  • 160
  • 165
  • 15
    This is good attempt, but has several flaws: 1. If cell was reused by the time the request finished, you could update the wrong row. 2. If user rapidly scrolls through the tableview, requests for visible cells can get back logged behind requests for cells that are no longer visible. We can remedy these issues, but I might instead suggest using `UIImageView` categories/extensions provided by SDWebImage or AFNetworking. – Rob May 22 '15 at 06:01
  • 1
    Yes,Its serious flaw.Image has to rendered from reloadTableView – Allamaprabhu Aug 18 '15 at 12:18
  • 5
    @DharmeshKheni This extension looks familiar ;) – Leo Dabus Sep 24 '15 at 17:27
  • @Rob what you think about this extension? :) – Dharmesh Kheni Sep 24 '15 at 18:17
  • 1
    It's better, and fine in trivial situations. But if on slow network or in situation where an image view might be reused (e.g. table views), it's problematic. It doesn't contemplate the cancelation of previous request if the image view can be reused (which means that visible cell requests will get backlogged behind requests for rows that are no longer visible and you'll see a flickering of images as various asynchronous image requests finish). It also doesn't do caching (other than what is provided automatically by `NSURLSession`). – Rob Sep 24 '15 at 18:56
  • 2
    I'd still consider one of the richer `UIImageView` extensions, such as https://github.com/Alamofire/AlamofireImage or https://github.com/onevcat/Kingfisher or https://github.com/rs/SDWebImage or https://github.com/kean/DFImageManager – Rob Sep 24 '15 at 18:56
17

Since I can't comment just yet, here's a Swift 3 (Xcode 8 Beta 6) version of the useful extension provided by Leo Dabus.

extension UIImageView {
    func downloadImageFrom(link:String, contentMode: UIViewContentMode) {
        URLSession.shared.dataTask( with: NSURL(string:link)! as URL, completionHandler: {
            (data, response, error) -> Void in
            DispatchQueue.main.async {
                self.contentMode =  contentMode
                if let data = data { self.image = UIImage(data: data) }
            }
        }).resume()
    }
}

I'm using this inside a class that populates the table cell, it works like this in that context just fine, just in case any newbs were wondering if it will:

albumArt.image = UIImage(named: "placeholder")
albumArt.downloadImageFrom(link: "http://someurl.com/image.jpg", contentMode: UIViewContentMode.scaleAspectFit)
Brad Root
  • 483
  • 5
  • 14
  • Worked like charm! Thanks. – Pankaj Gaikar Sep 18 '16 at 06:27
  • Unable to download image...getting error : TIC SSL Trust Error, NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) and Task <3E2...70>. HTTP load failed (error code: -1202). Any solution? – Jayprakash Dubey Jan 30 '18 at 04:33
  • @JayprakashDubey Are you trying to load from a HTTPS resource? There are some restrictions in iOS now that require HTTPS connections. – Brad Root Feb 15 '18 at 15:54
  • @BradRoot : Got this resolved by connecting to mobile data rather than office WiFi. Office WiFi has security restrictions. – Jayprakash Dubey Feb 16 '18 at 10:09
5

Details

  • Xcode 10.2.1 (10E1001), Swift 5

Full sample

Info.plist (add value)

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

Podfile

target 'stackoverflow-28694645' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for stackoverflow-28694645
  pod 'Alamofire'
  pod 'AlamofireImage'
end

Code

import UIKit
import Alamofire
import AlamofireImage

class ViewController: UIViewController {

    private weak var tableView: UITableView?
    private var items = [ItunceItem]()

    override func viewDidLoad() {
        super.viewDidLoad()
        let tableView = UITableView()
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
        tableView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
        tableView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
        tableView.tableFooterView = UIView()
        tableView.register(TableViewCell.self, forCellReuseIdentifier: "TableViewCell")
        tableView.rowHeight = 100
        tableView.separatorColor = .clear
        self.tableView = tableView
        loadData()
    }

    private func loadData() {
        let urlString = "https://itunes.apple.com/search?term=navigator"
        Alamofire.request(urlString).response { [weak self] response in
            guard let self = self, let data = response.data else { return }
            do {
                let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .convertFromSnakeCase
                self.items = try decoder.decode(ItunceItems.self, from: data).results
                DispatchQueue.main.async { [weak self] in
                    guard let tableView = self?.tableView else { return }
                    tableView.delegate = self
                    tableView.dataSource = self
                    tableView.reloadData()
                }
            } catch let error { print("\(error.localizedDescription)") }
        }
    }
}

extension ViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int { return 1 }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items.count }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell
        return cell
    }
}

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        guard   let cell = cell as? TableViewCell,
                let imageUrlString = items[indexPath.row].artworkUrl100,
                let url = URL(string: imageUrlString) else { return }
        cell.photoImageView?.af_setImage(withURL: url)
    }
    func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        guard let cell = cell as? TableViewCell else { return }
        cell.photoImageView?.af_cancelImageRequest()
    }
}

struct ItunceItems: Codable { let results: [ItunceItem] }
struct ItunceItem: Codable { var artworkUrl100: String? }

class TableViewCell: UITableViewCell {

    private(set) weak var photoImageView: UIImageView?
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        selectionStyle = .none
        let imageView = UIImageView()
        addSubview(imageView)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 6).isActive = true
        imageView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor).isActive = true
        imageView.leftAnchor.constraint(equalTo: safeAreaLayoutGuide.leftAnchor).isActive = true
        imageView.rightAnchor.constraint(equalTo: safeAreaLayoutGuide.rightAnchor).isActive = true
        imageView.contentMode = .scaleAspectFit
        photoImageView = imageView
    }

    required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
    override func prepareForReuse() {
        super.prepareForReuse()
        photoImageView?.image = nil
    }
}

Result

enter image description here

Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127