Andres Gomez
Andres Gomez

Reputation: 458

what should I do to load asynchronous images in tableView?

well, I should load images in the table view, I downloaded and loaded successful, but when I try to show in the table view, they doesn't appear, but, if I do scroll in the table view, the images will appear but the image won't in the middle of the cell.

I'm using swift 4.2

this lines helped me downloaded and loaded images

extension UIImageView {
    func downloaded(from url: URL, contentMode mode: UIView.ContentMode = .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() {
                self.image = self.resizedImageWith(image: image, targetSize: CGSize(width: 100.0, height: 50.0))
            }
            }.resume()
    }
    func downloaded(from link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit) {  
        guard let url = URL(string: link) else { return }
        downloaded(from: url, contentMode: mode)
    }
}

in my table view controller, I download the image with this function

func loadImages()
    {
        for img in arrFavServices
        {
            if let url = img?.logo
            {
                let imgDownload = UIImageView()
                imgDownload.downloaded(from: url, contentMode: .redraw)
                arrImages.append(imgDownload)
            }
            else
            {
                let imgDownload = UIImageView()
                imgDownload.image = UIImage(named: "logo")
                arrImages.append(imgDownload)
            }
          tableView.reloadData()
          tableView.layoutIfNeeded()
          tableView.layoutSubviews()
          utilActivityIndicator.shared.hideLoader(view: view)
        }
    }

the array arrFavServices contains all the images' url, and arrImages has all the images previously downloaded. the function loadImages was called in the viewdidload. and I use this function for show the images

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        if (arrFavServices[indexPath.section]?.logo) != nil
        {
            if arrImages[indexPath.section].image != nil
            {
                cell.imageView?.image = arrImages[indexPath.section].image
                cell.imageView?.contentMode = .center
            }
        }
        // Configure the cell...
        if let color = arrFavServices[indexPath.section]?.color
        {
            cell.backgroundColor = UIColor(hexString: color)
        }
        return cell
    }

what is my mistake? help meee please

Upvotes: 2

Views: 1802

Answers (3)

Shrikant Phadke
Shrikant Phadke

Reputation: 418

I will recommend using a Third-party library to load images in tableview.

add this in podfile.

pod 'SDWebImage'

add this simple code in cellForRowAtIndex

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            
            if(indexPath.row > arrId.count-1){
                // This will return an empty cell if all data is not downloaded and it will avoid the fatal error: index out of range
                return UITableViewCell()
            }
            else{
                let cell = tableView.dequeueReusableCell(withIdentifier: "AlbumCustomCell", for: indexPath) as! AlbumCustomCell
                cell.lblId.text = String ( self.arrId[indexPath.row] )
                cell.lblTitle.text = self.arrAlbumTitleString[indexPath.row]
                
                cell.layer.shouldRasterize = true
                cell.layer.rasterizationScale = UIScreen.main.scale
                
                if let url =  URL(string: self.arrAlbumThumbnailURLString[indexPath.row]) {
                    cell.albumThumbnaiilImage.sd_setImage(with: url, placeholderImage: UIImage(named: "placeholder"), options: .continueInBackground, context: nil)
                }
                return cell
            }
        }

This code is required to smoothly scroll the tableview.

cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale

I am downloading 5000 images and showing them in the table view.

Upvotes: 0

Vicaren
Vicaren

Reputation: 660

I think you have 2 options

  • You download image async when cell visible ( I recommend )

  • Download all images and show cell visible

If you are download all images increase your memory usage of app and if too much usage it, iOS will crash your app.

First path:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    if let logo = arrFavServices[indexPath.section]?.logo {
        // We need download image here
        cell.imageView?.downloaded(from: logo, contentMode: .center)
    }

    // Configure the cell...
    if let color = arrFavServices[indexPath.section]?.color {
        cell.backgroundColor = UIColor(hexString: color)
    }
    return cell
}

Second Path:

You can use dispatch group. UITableView is waiting for download all images.

// Cache array
var downloadedImages: [UIImage] = []

// Create an instance
var dispatchGroup = DispatchGroup()


func loadImages() {

    // Every tick of loop, we enter the group
    for img in arrFavServices {

        // Join the group
        dispatchGroup.enter()
        if let url = img?.logo {
            let imgDownload = UIImageView()
            imgDownload.downloaded(from: url, contentMode: .redraw, completion: { [weak self] downloadedImage in
                guard let self = self else { return }
                self.downloadedImages.append(downloadedImage)

                // And leave group when task is done
                dispatchGroup.leave()

            })
        } else {
            let imgDownload = UIImageView()
            imgDownload.image = UIImage(named: "logo")
            arrImages.append(imgDownload)

            // We can leave here too because we add image to array
            dispatchGroup.leave()
        }

    }

    // We have to listen group, and that we update tableView or UI todo
    dispatchGroup.notify(queue: .main) {
        self.tableView.reloadData()
        self.tableView.layoutIfNeeded()
        self.tableView.layoutSubviews()
        self.utilActivityIndicator.shared.hideLoader(view: self.view)
    }
}

You can set completion handler like below

extension UIImageView {
    func downloaded(from url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit, completion: ((UIImage) -> Void)?) {
        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() {
                completion?(image)
            }
            }.resume()
    }
    func downloaded(from link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit, completion: ((UIImage) -> Void)?) {
        guard let url = URL(string: link) else { return }
        downloaded(from: url, contentMode: mode, completion: completion)
    }
}

Upvotes: 3

Abhishek
Abhishek

Reputation: 329

Mate, why are you not using third party library? Sd_webimage, Kingfisher you can just pod one of this library and in one line of code your image is visible at your desired index of tableview.

Upvotes: -1

Related Questions