Raffi Raffi - 4 months ago 15x
Swift Question

Why is it wrong to update UITableView's cells this way?

I am making a download manager I have been struggling with updating the cells because of the "reusable cells"....

I had problems with updating the cells after a scroll but I found a workaround that doesn't work properly, this way is leading the app to crash after the cell becomes unvisible, I don't know why I hope you guys can explain, and if you know how to fix it please let me know.

I add a download task this way

func addDownloadTask(URL: NSURL) {

let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
let mainQueue = NSOperationQueue.mainQueue()
let session = NSURLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: mainQueue).downloadTaskWithURL(URL)

let newDownload = RCHDownloadAddictModelClass(fileURL: URL)

newDownload.downloadTask = session


newDownload.downloadIndex = downloadingArray.indexOf(downloadingArray.last!)




When a
begins downloading this method get invoked

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {

for (index, downloadModel) in downloadingArray.enumerate() {
if downloadTask.isEqual(downloadModel.downloadTask) {
dispatch_async(dispatch_get_main_queue(), {

let indexPath = NSIndexPath(forRow: index, inSection: 0)
/* xCode stops here when the app crashes */ let cell = self.tableView.cellForRowAtIndexPath(indexPath) as! RCHDownloadAddictTableViewCell
let countOfBytesWritten: Double!
if totalBytesExpectedToWrite < 0 {

countOfBytesWritten = 0

} else {

countOfBytesWritten = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)


downloadModel.fileName = downloadTask.response?.suggestedFilename
downloadModel.downloadIndex = downloadingArray.indexOf(downloadModel)
downloadModel.downloadSize = Double(totalBytesExpectedToWrite)
downloadModel.downloadProgress = Float(countOfBytesWritten)
downloadModel.downloadTaskIdentifier = downloadTask.taskIdentifier
downloadModel.didFinishDownload = false

self.updateCell(cell, forRowAt: indexPath)



and this is the way the cell gets updated:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCellWithIdentifier("downloadCell", forIndexPath: indexPath) as! RCHDownloadAddictTableViewCell

updateCell(cell, forRowAt: indexPath)

return cell

func updateCell(cell: RCHDownloadAddictTableViewCell, forRowAt indexPath: NSIndexPath) {

let arrayInfo = downloadingArray[indexPath.row]

cell.cellProgressView.setProgress((arrayInfo.downloadProgress)!, animated: true)
cell.cellFileName.text = arrayInfo.fileName
cell.cellDownloadSpeed.text = String(format: "%.1fMB", (arrayInfo.downloadSize / 1000000))
cell.cellBlock = {

switch arrayInfo.downloadTask.state {
case .Running:
cell.cellButton.setImage(UIImage(named: "resume.png"), forState: UIControlState.Normal)
case .Suspended:
cell.cellButton.setImage(UIImage(named: "pause.png"), forState: UIControlState.Normal)
cell.cellButton.setImage(UIImage(named: "resume.png"), forState: UIControlState.Normal)


if (arrayInfo.didFinishDownload == true) {
cell.cellButton.hidden = true
cell.cellFinishIndicator.text = "Finished."
cell.cellProgressView.hidden = true
cell.cellFinishIndicator.hidden = false
} else {
cell.cellButton.hidden = false
cell.cellProgressView.hidden = false
cell.cellFinishIndicator.hidden = true



you can see it at the apple doc :

public func cellForRowAtIndexPath(indexPath: NSIndexPath) -> UITableViewCell? 
// returns nil if cell is not visible or index path is out of range

//cell will be nil when this row is not visible
let cell = self.tableView.cellForRowAtIndexPath(indexPath) as! RCHDownloadAddictTableViewCell 

then you call self.updateCell(cell, forRowAt: indexPath) it will crash