Search code examples
iosswiftnsoperationqueuensoperationuiactivityindicatorview

How to fix UIActivityIndicatorView from startAnimation again in an UIImageView Extension used in a TableView


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 ?


Solution

  • 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.