Swift 3 : URL Image makes UITableView scroll slow issue

我怕爱的太早我们不能终老 提交于 2019-12-01 12:03:29

I think, that problem here, that you need to cache your images in table view to have smooth scrolling. Every time your program calls cellForRowAt indexPath it downloads images again. It takes time.

For caching images you can use libraries like SDWebImage, Kingfisher etc.

Example of Kingfisher usage:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath) as! CustomCell

    cell.yourImageView.kf.setImage(with: URL) 
    // next time, when you will use image with this URL, it will be taken from cache.

    //... other code
}

Hope it helps

You can use the frameworks as suggested here, but you could also consider "rolling your own" extension as described in this article

"All" you need to do is:

  1. Use URLSession to download your image, this is done on a background thread so no stutter and slow scrolling.
  2. Once done, update your image view on the main thread.

Take one

A first attempt could look something like this:

func loadImage(fromURL urlString: String, toImageView imageView: UIImageView) {
    guard let url = URL(string: urlString) else {
        return
    }

    //Fetch image
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        //Did we get some data back?
        if let data = data {
            //Yes we did, update the imageview then
            let image = UIImage(data: data)
            DispatchQueue.main.async {
                imageView.image = image
            }
        }
    }.resume() //remember this one or nothing will happen :)
}

And you call the method like so:

loadImage(fromURL: "yourUrlToAnImageHere", toImageView: yourImageView)

Improvement

If you're up for it, you could add a UIActivityIndicatorView to show the user that "something is loading", something like this:

func loadImage(fromURL urlString: String, toImageView imageView: UIImageView) {
    guard let url = URL(string: urlString) else {
        return
    }

    //Add activity view
    let activityView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
    imageView.addSubview(activityView)
    activityView.frame = imageView.bounds
    activityView.translatesAutoresizingMaskIntoConstraints = false
    activityView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor).isActive = true
    activityView.centerYAnchor.constraint(equalTo: imageView.centerYAnchor).isActive = true
    activityView.startAnimating()

    //Fetch image
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        //Done, remove the activityView no matter what
        DispatchQueue.main.async {
            activityView.stopAnimating()
            activityView.removeFromSuperview()
        }

        //Did we get some data back?
        if let data = data {
            //Yes we did, update the imageview then
            let image = UIImage(data: data)
            DispatchQueue.main.async {
                imageView.image = image
            }
        }
    }.resume() //remember this one or nothing will happen :)
}

Extension

Another improvement mentioned in the article could be to move this to an extension on UIImageView, like so:

extension UIImageView {
    func loadImage(fromURL urlString: String) {
        guard let url = URL(string: urlString) else {
            return
        }

        let activityView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
        self.addSubview(activityView)
        activityView.frame = self.bounds
        activityView.translatesAutoresizingMaskIntoConstraints = false
        activityView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
        activityView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
        activityView.startAnimating()

        URLSession.shared.dataTask(with: url) { (data, response, error) in
            DispatchQueue.main.async {
                activityView.stopAnimating()
                activityView.removeFromSuperview()
            }

            if let data = data {
                let image = UIImage(data: data)
                DispatchQueue.main.async {
                    self.image = image
                }
            }
        }.resume()
    }
}

Basically it is the same code as before, but references to imageView has been changed to self.

And you can use it like this:

yourImageView.loadImage(fromURL: "yourUrlStringHere")

Granted...including SDWebImage or Kingfisher as a dependency is faster and "just works" most of the time, plus it gives you other benefits such as caching of images and so on. But I hope this example shows that writing your own extension for images isn't that bad...plus you know who to blame when it isn't working ;)

Hope that helps you.

For caching image in background & scroll faster use SDWebImage library

 imageView.sd_setImage(with: URL(string: "http://image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))

https://github.com/rs/SDWebImage

Your tableview slow because you load data in current thread which is main thread. You should load data other thread then set image in main thread (Because all UI jobs must be done in main thread). You do not need to use third party library for this just change your extension with this:

extension UIImageView{
func setImageFromURl(stringImageUrl url: String){
    if let url = NSURL(string: url) {
        DispatchQueue.global(qos: .default).async{
            if let data = NSData(contentsOf: url as URL) {
                DispatchQueue.main.async {
                    self.image = UIImage(data: data as Data)
                }
            }
        }
    }
 }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!