【问题标题】:TableView loads cell before API request can update modelTableView 在 API 请求可以更新模型之前加载单元格
【发布时间】:2019-08-10 00:43:51
【问题描述】:

我有一个 tableView 控制器,其中每一行将代表特定加密货币的价值。对于用户想要跟踪的每种不同的加密货币,我都有一个名为 CryptoCurrency 的类。在 TableView CellforRowAt 函数下,我正在调用另一个名为 getCryptoData 的函数,它将使用 AlamoFire 发送 api 请求以获取每种加密货币的价格。

问题在于 cellForRowAt 函数在 getCryptoData 函数完成并更新模型之前返回一个默认价格为 0.00 的单元格。

我假设这是因为该函数是异步运行的?

如何让它在返回单元格之前等待函数完成或在完成后重新加载单元格?

我尝试在updateCryptoData 函数的末尾添加tableView.reloaddata(),但这导致了无限循环。

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "CryptoCurrencyCell", for: indexPath)        

    let cryptoCurrencyItem = cryptoCurrencyContainer.listOfCryptoCurrencies[indexPath.row]
    getCryptoData(url: getApiString(for: cryptoCurrencyItem.id), currencyItem: cryptoCurrencyItem)

    cell.textLabel?.text = cryptoCurrencyContainer.listOfCryptoCurrencies[indexPath.row].name + "\(cryptoCurrencyItem.currentPrice)"
    print(cryptoCurrencyItem.currentPrice)
    return cell
}

func getCryptoData(url: String, currencyItem: CryptoCurrency) {
    Alamofire.request(url, method: .get).responseJSON {
        response in
            if response.result.isSuccess {
                print("Sucess! bitcoin data")
                let cryptoDataJSON : JSON = JSON(response.result.value!)

                print(cryptoDataJSON)
                self.updateCryptoData(json: cryptoDataJSON, currencyItem: currencyItem)
            } else {
                print("oops")
                print("Error: \(String(describing: response.result.error))")
                //self.bitcoinPriceLabel.text = "Connection Issues"
            }
    }
}

func updateCryptoData(json : JSON, currencyItem: CryptoCurrency) {
    if let cryptoPriceResult = json["ask"].double {
        //bitcoinPriceLabel.text = String(bitcoinResult)
        currencyItem.updatePrice(price: cryptoPriceResult)
        print(currencyItem.currentPrice)
    } else {
        //bitcoinPriceLabel.text = "Price Unavailable"
        print("something aint right")
    }
}

cellForRowAt 函数下有一条打印语句:

print(cryptoCurrencyItem.currentPrice)

在单元格返回之前捕获当前价格。控制台显示仍为 0.00,表示 getCryptoData 函数尚未完成运行。

【问题讨论】:

  • cellForRowAt 是执行不必要的异步任务的错误位置。此方法的目的只是将模型的值分配给相应的 UI 元素。在 viewDidLoadviewWillAppear 甚至在模型中执行异步操作,并在所有任务完成后重新加载表格视图。

标签: ios swift


【解决方案1】:

可能这不是最好的解决方案,但您可以将单元格传递给您的方法

func getCryptoData(url: String, currencyItem: CryptoCurrency, for cell: UITableViewCell) {
    Alamofire.request(url, method: .get).responseJSON {
        response in
            if response.result.isSuccess {
                print("Sucess! bitcoin data")
                let cryptoDataJSON : JSON = JSON(response.result.value!)

                print(cryptoDataJSON)
                self.updateCryptoData(json: cryptoDataJSON, currencyItem: currencyItem, for: cell)
            } else {
                print("oops")
                print("Error: \(String(describing: response.result.error))")
                //self.bitcoinPriceLabel.text = "Connection Issues"
            }
    }

}

func updateCryptoData(json : JSON, currencyItem: CryptoCurrency, for cell: UITableViewCell) {
    if let cryptoPriceResult = json["ask"].double {
        //bitcoinPriceLabel.text = String(bitcoinResult)
        currencyItem.updatePrice(price: cryptoPriceResult)
        print(currencyItem.currentPrice)
        cell.textLabel?.text = "\(cryptoPriceResult)" // your cell
    } else {
        //bitcoinPriceLabel.text = "Price Unavailable"
        cell.textLabel?.text = "\(cryptoPriceResult)" // your cell
        print("something aint right")
    }
}

由于UITableViewCell是引用类型,一旦异步调用完成,就会更新单元格文本标签。

【讨论】:

    【解决方案2】:

    您的请求正在后台线程中运行。这意味着您的 tableView 在您从请求中获取数据之前运行。因此,当您在 updateCryptoData 方法中获得成功时,您应该在主线程中调用 tableView.reloadData()

    DispatchQueue.main.async {
        tableView.reloadData()
    }
    

    此代码将运行 tableView cellForRow 方法

    【讨论】:

      【解决方案3】:

      是的,updateCryptoData 函数末尾的 tableView.reloaddata() 将导致无限循环,因为在每次重新加载 api 调用后,您再次调用 api 并继续循环。

      从多种方法中的一种,您可以拥有基于键值的数据结构。

      在您的 cellForRowAt 函数中,如果您的标签可用,您将从数据结构中分配值。如果您的数据结构中没有可用的值,则显示一些默认值,例如“正在加载”或微小的加载轮 UI。

      cell.priceLabel?.text = dataStructre["key"]
      

      现在,此密钥可能是您识别货币项目的某个唯一 ID。

      在从单元格调用您的 api 之前,请事先检查“key”的值是否在您的数据结构中可用。如果值不可用,那么只有您将调用 api 否则从数据结构中简单获取并将其设置为您的标签。

      if let value = dataStructre["key"]{
         cell.priceLabel?.text = value
      }
      else{
         callApi()
      }
      

      最后,在您的 api 调用的成功方法中,简单地将您从服务器获得的值存储在带有“密钥标识符”的数据结构中。保存到您的数据结构后,只需调用“reloadData”即可。

      function callApi(){
         if(success){
            dataStructure["key"] = value from server
            table.reloadData()
         }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-05
        • 2019-10-22
        • 1970-01-01
        • 2018-10-24
        • 1970-01-01
        • 1970-01-01
        • 2020-10-20
        • 1970-01-01
        相关资源
        最近更新 更多