Search code examples
iosswiftuitableviewalamofireuiprogressview

A table with cells that all have their own progress views is very slow


I have a table which displays information about a list of objects being downloaded. Each cell contains a UIProgressView that is updated according to the progress of that download. My problem is that during the download and updating of this progress view, everything is very slow, and the CPU is monitored at 100% or more in the debug navigator of Xcode.

Each object is stored with its own progress property.

class Download {
    var id: String = ""
    var progress: Float = 0
}

And I have an array of these:

var downloads = [Download]()

This is set to the tableView in my view controller using the cellForRowAtIndexPath: using something along the lines of:

let download = downloads[indexPath.row]
cell.progressView.progress = download.progress 

I'm using Alamofire to manage my downloads and using the included progress closure, I am updating the downloads' progresses:

download.progress = Float(bytesRead) / Float(bytesExpected)

dispatch_async(dispatch_get_main_queue()) {
    self.tableView.reloadData()
}

It's at this point where I think the bottleneck is occurring due to the large number of updates happening. To try limit the problem, I tried out only updating a single cell as it was changed:

let rowIndex = self.downloads.indexOf() { $0.id == download.id }                 
let indexPath = NSIndexPath(forRow: rowIndex!, inSection: 0)

dispatch_async(dispatch_get_main_queue()) {
    self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
}

However I found that this caused everything to go even slower.

Any suggestions would be greatly appreciated, thank you in advance!


Solution

  • The upload progress block can be triggered many times per second, and reloading the whole table view is expensive, and inefficient. What you should do instead is to monitor the upload progress in each table view cell individually. Here is how you can do it. In your tableviewcell:

      var progressTimer: NSTimer?
    
      var download: Download? {
        didSet {
          guard let download = download else {
            // cancel your timer here
            return
          }
          // start NSTimer here to update UIProgressView every second
        }
      }
    
      func prepareForReuse() {
        super.prepareForReuse()
        download = nil
      }
    

    In your view controller:

    override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell,
        forRowAtIndexPath indexPath: NSIndexPath) {
          cell.download = downloads[indexPath.row]
      }