2

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!

Matt Le Fleur
  • 2,708
  • 29
  • 40
  • The problem is that you are reloading the whole table view, instead of just updating the UIProgressView. – almas Mar 10 '16 at 18:19

2 Answers2

3

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]
  }
almas
  • 7,090
  • 7
  • 33
  • 49
  • Thanks for this! Definitely reduced the load, still slightly slow, but I'm sure this is due to everything else that's going on in the app :\ – Matt Le Fleur Mar 11 '16 at 10:54
  • Glad that helped. You can either use time profiler to identify what slows the app down. Or just comment out pieces of code and see how the app behaves. For example, comment out "cell.download = downloads[indexPath.row]", so that cells stop tracking any progress, and then test the responsiveness. – almas Mar 11 '16 at 18:45
0

Your are trying to reload the table or the cell when progress updated, that's heavy.

Since the progressViews have been created, try to reuse them, you can get a progressView instance using Tag:

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


    let cell = tableView.cellForRowAtIndexPath(indexPath)

    let progressView = cell?.viewWithTag(100) // Tag your progressView as 100

    progressView.progress = download.progress

Go further, you can also tune the performance by only update progress every 0.5 sec or shorter, as long as user feel it go smoothly. But not for every progressChanged, that would be too frequent.

dichen
  • 1,643
  • 14
  • 19