0

I have an UIImageView extension with a method to download an set image, the extension also contains an UIActivityIndicatorView which i set as a view before the image is loaded once the image is loaded i remove or hide the UIActivityIndicatorView from the UIImageView

extension UIImageView {

    private static let imageProcessingQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".imageprocessing", attributes:  .concurrent)

    var activityIndicator: UIActivityIndicatorView! {
        get {
            return objc_getAssociatedObject(self, &activityIndicatorAssociationKey) as? UIActivityIndicatorView
        }
        set(newValue) {
            objc_setAssociatedObject(self, &activityIndicatorAssociationKey, newValue, .OBJC_ASSOCIATION_RETAIN)
        }
    }

    func showActivityIndicatory() {

        // activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
        activityIndicator = UIActivityIndicatorView(frame: CGRect.init(x: 0, y: 0, width: 50, height: 50))
        activityIndicator.center = self.center
        activityIndicator.hidesWhenStopped = true
        activityIndicator.style = UIActivityIndicatorView.Style.gray

        self.isUserInteractionEnabled = false

        UIImageView.imageProcessingQueue.async {
            DispatchQueue.main.async {
                self.activityIndicator.startAnimating()
                self.addSubview(self.activityIndicator)

             }
        }
    }

    func hideIndicator() {
        UIImageView.imageProcessingQueue.async {
            DispatchQueue.main.async {
                self.activityIndicator.stopAnimating()
                self.activityIndicator.removeFromSuperview()

            }
        }
    }

    func setImage(url : String)  {
        self.showActivityIndicatory()
        operatorManager.loadImage(url: url) { (image) in
            self.image = image
            self.hideIndicator()
        }
    }
}

And then i can use this extension function like this,

guard let cell = tableView.dequeueReusableCell(withIdentifier:"Tableview", for: indexPath) as? TableViewCell else {
    fatalError()
}

let imagees = list[indexPath.row]

cell.labelView.text = imagees.urlString
cell.iconView.setImage(url: imagees.urlString)

This is the Operation for loading image from Cache if available or from a server.

    class LoadImageOperation: ConcurrentOps<UIImage> {

private let session: URLSession
private let url: String
private var task: URLSessionTask?
private var defaultImage: UIImage?
// MARK: - Init

init(session: URLSession = URLSession.shared, url: String) {
    self.session = session
    self.url = url
    self.defaultImage = UIImage(named: "icons")
}


override func main() {

    guard let imageURL = URL(string: url) else {
        cancel()
        return
    }



    if let cachedImage = DataCache.shared.object(forKey: url) {

        DispatchQueue.main.async {
        self.complete(result: cachedImage as! UIImage)

        }
       cancel()
    }


    task = session.downloadTask(with: imageURL){ (url,response,error) in

        guard let url = url ,
            let data = try? Data(contentsOf: url),
            let image = UIImage(data: data) else {

                DispatchQueue.main.async {

                    if error != nil {
                        self.complete(result: self.defaultImage!)
                    } else {
                         self.complete(result: self.defaultImage!)
                    }
                }
                return
        }
        DispatchQueue.main.async {

            DataCache.shared.saveObject(object: image, forKey: self.url)
            self.complete(result: image)
        }

    }
    task?.resume()


}

}

The operation is managed here ,

func loadImage(url: String, completionHandler: @escaping (_ result: UIImage) ->Void) {

    let operation = LoadImageOperation(url: url)

    operation.completionHandler = completionHandler
    operation.name = "loadImageOperation"
    queueManager.enqueue(operation)

}

When my tableView first starts loading the indicator shows and hides perfectly, but when i scroll the tableview , it starts animating the indicator again on the UIImageView. How do i prevent this ?

BigFire
  • 317
  • 1
  • 4
  • 17

2 Answers2

0

The reason the activity indicator is showing is that when scrolling, cells are reused (for performance reasons) to display new data. This means that every time you scroll to a new cell, it will load and call cellForRow, calling the function that loads the activity indicator as well.

You should give more information on how you're handling the UITableView if you need a step by step answer, but I think you might want to implement image caching to avoid loading an image every time you load a cell. Check this answer for more info.

Renzo Tissoni
  • 642
  • 9
  • 21
  • which other information do you need ,Is the code provided not enough? Because i am not doing anything special in the ViewController containing the tableview. – BigFire May 13 '19 at 14:55
  • Two questions: 1. Do you have one activity indicator for each image, or just a general one? 2. Is the last piece of code the full `cellForRow` function? – Renzo Tissoni May 13 '19 at 15:05
  • The Link you provided contains how to Cache downloaded images , which has already been handled in the `operatorManager.loadImage` which is called in the `setImage` function, provided in the `UIImageView` Extension. if you look at the Extension i posted carefully the `UIActivityIndicatorView ` is created and used when im about to download an Image and its hides itself when the `completionHandler` returns an Image. The last 2 piece of code is the fullcode for `cellForRow` – BigFire May 13 '19 at 15:25
  • Then the problem might be in `loadImage`. Post it if you can. – Renzo Tissoni May 13 '19 at 15:48
  • Is the problem from the `loadImage` ? – BigFire May 13 '19 at 17:47
0

The solution was just simple for me to believe until i tried it out.Its not the best out there but it fixed my problem.

    func setImage(url : String)  { 
    if DataCache.shared.object(forKey: url) != nil {
              self.hideIndicator()
    }else{
        self.showActivityIndicatory()
    }
    operatorManager.loadImage(url: url) { (image) in
        self.image = image
        self.hideIndicator()
    }
}

I checked if the image its cached before i even run my Operation, if its cached i hide the UIActivityIndicatorView else i show it . why i think this is not 100% is that, the operation is also checking for cached images which i think can increase CPU.

BigFire
  • 317
  • 1
  • 4
  • 17