3

I have an extension to print image URL on UIImageView. But I think the problem is my tableView is so slow because of this extension. I think I need to open thread for it. How can I create a thread in this extension or do you know another solution to solve this problem?

My code :

extension UIImageView{

    func setImageFromURl(stringImageUrl url: String){

        if let url = NSURL(string: url) {
            if let data = NSData(contentsOf: url as URL) {
                self.image = UIImage(data: data as Data)
            }
        }
    }
}
jorjj
  • 1,479
  • 4
  • 20
  • 36
  • use SDWebImage for image caching https://github.com/rs/SDWebImage – Developer Jun 13 '17 at 11:14
  • Never perform lengthy operations like downloading content of URL on main thread. Main thread is associated with MainQueue which is a serialized queue so obviously keeping the main thread busy will cause all your UI actions wait in main queue which will result in jerky animation.Use dispatch queues to download the content and switch to main thread to update the UI – Sandeep Bhandari Jun 13 '17 at 11:24
  • thanks for your advices. I used kingfisher framewrokd and it also uses dispatch queues as well. – jorjj Jun 13 '17 at 11:29

4 Answers4

3

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.

pbodsk
  • 6,787
  • 3
  • 21
  • 51
  • 1
    yeah, you are totally right. I think it's better to write your own extensions instead of using frameworks. then you don't need to be worried about if they update their framework for the future or not. – jorjj Jun 13 '17 at 13:38
  • This will download image every time, while SDWebimage caches once it's loaded. – Jack Jun 14 '17 at 02:45
  • @Jack Yes I know, that is why I wrote "plus it gives you other benefits such as caching of images and so on" about SDWebImage. But...the intention of this answer was just to show that for a seemingly simple thing such as downloading an image, there is no need to add another dependency to your project _if_ you can live with a simpler solution that you've written yourself. – pbodsk Jun 14 '17 at 06:42
2

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

Vlad Pulichev
  • 3,162
  • 2
  • 20
  • 34
2

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)
                }
            }
        }
    }
 }
}
Alperk
  • 106
  • 4
1

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

Jack
  • 13,571
  • 6
  • 76
  • 98