【问题标题】:API async call in UITableView cellForRowAtUITableView cellForRowAt 中的 API 异步调用
【发布时间】:2018-11-01 13:43:56
【问题描述】:

我有 UITableView 来显示文件列表。每个文件名都使用特定的代码组合进行编码,为了获得真实的文件名,我必须使用当前文件名调用我的服务器端。

是否可以在 cellForRowAt indexPath 表视图委托函数上调用这样的操作?

var files: [URL]!

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: "FilesManagerCell", for: indexPath) as? FilesManagerCell {
            let currentFile = files[indexPath.row]

            APIClient.getFileInfo(patientId: currentFile.lastPathComponent, onSuccess: { (response) in
                cell.filenameLabel.text = response.file.fileName
                cell.createdDateLabel.text = response.file.creationDate
                cell.fileSizeLabel.text = response.file.fileSizeWithToken               
                cell.filePath = currentFile
                return cell <- Here I get expected error

           }, onError: { (errorResponse, errorString) in                
        })

        }
        return UITableViewCell()

【问题讨论】:

  • 将您的请求移至 FilesManagerCell 类,以便它可以异步处理请求。如果单元被重用,您还需要覆盖 prepareForReuse 以停止请求。所以cellForRowAt函数应该只配置cell,可以创建downloadFile(_ filePath: String)之类的函数来处理API请求。
  • 除此之外无论如何都不能工作,不要那样做。 cellForRow 用于更新和显示 UI 元素。在viewDidLoad/viewWillAppear 甚至模型中执行异步任务。考虑到用户滚动时可以随时释放单元格。

标签: ios swift uitableview


【解决方案1】:

cellForRowAt 要求您同步返回一个单元格。

根据请求,通过每个单元格加载其内容的责任来自定义您的单元格,从而重新加载其自己的 UI。

举例说明:

这将是您的手机(您的插座已连接):

class FilesManagerCell: UITableViewCell {

    @IBOutlet private weak var filenameLabel: UILabel!
    @IBOutlet private weak var createdDateLabel: UILabel!
    @IBOutlet private weak var fileSizeLabel: UILabel!

    var filePath: URL! {
        didSet {
            fetchContents()
        }
    }

    override func awakeFromNib() {
        super.awakeFromNib()

        //Here you should create the "Loading behavior", i.e:
        self.filenameLabel.text = "Loading"
        self.createdDateLabel.text = "Loading"
        self.fileSizeLabel.text = "Loading"          
    }

    private func fetchContents() {
        APIClient.getFileInfo(patientId: filepath.lastPathComponent, onSuccess: { response in
            DispatchQueue.main.async {
                self.filenameLabel.text = response.file.fileName
                self.createdDateLabel.text = response.file.creationDate
                self.fileSizeLabel.text = response.file.fileSizeWithToken               
            }
        }, onError: { (errorResponse, errorString) in
            //Customize your error code, i.e:
            DispatchQueue.main.async {
                self.filenameLabel.text = "Error"
                self.createdDateLabel.text = "Error"
                self.fileSizeLabel.text = "Error"
            }
        })
    }
}

还有你的 tableView cellForRowAt 实现:

//...
var files: [URL]!

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: "FilesManagerCell", for: indexPath) as? FilesManagerCell {     
        let currentFile = files[indexPath.row]
        cell.filePath = currentFile
    }

    return UITableViewCell()
}
//...

编辑:将此责任委托给单元格本身没有问题,但请注意,当单元格被重用时,它会在设置filePath时触发新的fetchContents()属性,从而触发一个新的 API 调用——但是会有一些数据“不一致”,因为当 fetch 没有返回时,它会有之前的 fetch 内容。一种解决方案是每次设置filePath 时都重置为“正在加载”状态,如下所示:

    var filePath: URL! {
        didSet {
            // Reset the current fields
            self.filenameLabel.text = "Loading"
            self.createdDateLabel.text = "Loading"
            self.fileSizeLabel.text = "Loading"

            // And then fetch the contents
            fetchContents()
        }
    }

【讨论】:

  • 请记住,在完成处理程序中,您需要确认单元格仍然有效;它可能在异步操作期间被重用。
  • @MattComi 你是对的,我已经添加了一个关于这个的编辑,谢谢你提醒我。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-09-01
  • 2021-07-24
  • 1970-01-01
  • 2015-09-14
  • 2019-07-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多