简体   繁体   English

为什么用这种方式更新UITableView的单元格是错误的?

[英]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

    downloadingArray.append(newDownload)

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

    self.tableView.reloadData()

    session.resume()

}

When a NSURLSessionDownloadTask begins downloading this method get invoked NSURLSessionDownloadTask开始下载时,将调用此方法

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:
            arrayInfo.downloadTask.suspend()
            cell.cellButton.setImage(UIImage(named: "resume.png"), forState: UIControlState.Normal)
        case .Suspended:
            arrayInfo.downloadTask.resume()
            cell.cellButton.setImage(UIImage(named: "pause.png"), forState: UIControlState.Normal)
        default:
            arrayInfo.downloadTask.suspend()
            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 : 您可以在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 然后您调用self.updateCell(cell, forRowAt: indexPath)将会崩溃

You are on the right path with your code with one exception. 除了一个例外,您的代码正确无误。

After your download has finished and you have stored the correct data into the array in the correct place you should run the code... 下载完成后,将正确的数据存储到数组中的正确位置,您应该运行代码...

tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation:.Automatic)

This will then trigger the table to reload the cell at the correct index path. 然后,这将触发表以正确的索引路径重新加载单元。 If that cell is on screen it will reload. 如果该单元格在屏幕上,它将重新加载。 If that cell is not on screen it will not reload. 如果该单元格不在屏幕上,它将不会重新加载。 But the next time the cell is displayed it will now show the correct information. 但是,下次显示该单元格时,它将显示正确的信息。

In essence. 在本质上。 Don't update the cell directly. 不要直接更新单元格。 Just tell the tableview that you have changed the data for that particular row. 只需告诉tableview您已更改了该特定行的数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM