如何在 Xcode swift 的 UITableview 单元格中显示当前股票价格?

Posted

技术标签:

【中文标题】如何在 Xcode swift 的 UITableview 单元格中显示当前股票价格?【英文标题】:How can I display the current stock price in a UITableview cell in Xcode swift? 【发布时间】:2021-03-26 19:41:03 【问题描述】:

我创建了一个 UITableview 并添加了一些显示几只股票(Apple、Tesla)名称的单元格。我还在我想要显示股票当前股价的单元格中添加了一个右细节文本标签。到目前为止,使用 Finnhub.io,我能够创建一个 API 调用并将当前价格数据存储在一个名为 decodedData 的变量中。我还能够在我的调试控制台中打印出这些数据。我目前面临的唯一问题是没有在 UI 单元格中显示调试控制台的数据。如果有人对如何解决此问题有任何想法,请告诉我。

这是我进行 API 调用和获取 URL 的代码:

struct StockManager 
    
    let decoder = JSONDecoder()
    var returnValue:String = ""
    
   
    
    let stockUrl = "https://finnhub.io/api/v1/quote?token=AUTH_TOKEN"
    
    mutating func fetchStock(stockName: String) -> String
        var stockValue:String = ""
        
        let urlString = "\(stockUrl)&symbol=\(stockName)"
        stockValue = performRequest(urlString: urlString)
        
        return stockValue
    
    
    func performRequest(urlString: String) -> String  
        
        var retValue: String = ""
        
        
        //Create a URL
        
        if let url = URL(string: urlString)
            
            //Create a url session
            let session = URLSession(configuration: .default)
            
            //Create task
            
            let task = session.dataTask(with: url)  (data, response, error) in
                if error != nil
                    print(error)
                    return
                
                
                if let safeData = data
                self.parseJSON(stockData: safeData)
                
                //Start task
                
            
            task.resume()
        
        
        
        return retValue
    
    
    func parseJSON(stockData: Data) -> String 
        
        var returnValue: String = ""
        var jsonValue: String = ""
        
        do
            let decodedData = try decoder.decode(StockData.self, from: stockData)
            let c:String = String(decodedData.c)
            let h:String = String(decodedData.h)
            let l:String = String(decodedData.l)
            
            jsonValue =  String(decodedData.c)
            
            print(decodedData.c)
                        
          //  let stockData = StockData(c: c, h: h, l: l)
            
            
        catch
            print("Error decoding, \(error)")
        
        
        return jsonValue

    
    

这是我创建表格视图和单元格的代码:

var listOfStocks = ["AAPL", "TSLA"]
var listOfStocks2 = ["Apple": "AAPL", "Tesla": "TSLA"]


var stockManager = StockManager()

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
    return listOfStocks.count


override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell  
    // create cell
    let cell = tableView.dequeueReusableCell(withIdentifier: "StockListCell", for: indexPath)
    let jsonValue: String = ""
    
    cell.textLabel?.text = listOfStocks[indexPath.row]

    
    if let stock = cell.textLabel?.text
        
        stockManager.fetchStock(stockName: stock)
        cell.detailTextLabel?.text = stock

    
    return cell

我希望能够通过在上面的函数中编码来显示我当前的股票价格 (覆盖 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath))。如果有人对此问题有答案,请告诉我,谢谢!

【问题讨论】:

我认为 Rich 在这方面做得很好。 【参考方案1】:

所以第一个问题是您的网络调用在您编写时无法正常工作。您编写的方法是同步的,这意味着它立即返回一个值,而它包含的网络调用是异步的,这意味着它会在一段时间后返回某种值(您想要的数据或错误),使用dataTask 回调(块)。

因此您需要更改代码的工作方式。有很多方法可以做到这一点:我将勾勒出一种可能的简单方法,它不会涵盖所有情况,但至少会为您提供一些可行的方法。在这种情况下,我们可以添加一个方法并在viewDidLoad 中调用它,而不是在每次调用-cellForRowAtIndexPath: 时进行网络调用。这个方法看起来像这样:

func loadStocks() 
    self.stockData = []

    self.stockManager.fetchStocks(
      stockNames: listOfStocks,
      callback: updateTable(data:error:))
  

(如果您第二次获取,我们将 [] 分配给实例变量以清除先前的结果 - 例如,如果您可能想要添加拉动刷新方法以及 viewDidLoad 调用。)

UpdateTable 是视图控制器上的另一种方法;看起来像这样

func updateTable(data: StockData?, error: Error?) 

    DispatchQueue.main.async  [weak self]  in
      if let data = data  
        guard let self = self else  return 
        self.stockData.append(data)
        self.tableView.reloadData()
       else if let error = error  
        print("failed to load data: \(error)")
        //or handle the error in some other way, 
      
    
  

我们在这里将它作为参数传递,因此它可以用作回调,而不是直接调用它

它会在每个网络请求完成时被调用:如果它成功,你会得到一个StockData对象:如果它失败,一个Error。参数是可选的,因为我们不知道它会是哪个。然后我们将新数据添加到StockData(驱动表)的实例变量数组中,并在 tableView 上调用 reload。 DispatchQueue.main.async 存在是因为网络请求将在后台线程上返回,我们无法从那里更新 UI - UI 必须始终在主线程上更新。 [weak self]guard let self = self else return 是一个烦人的细节,需要确保我们没有得到参考周期。

StockDataManager 和 StockData 对象现在如下所示:

struct StockData: Codable 

  var name: String?
  let c: Double
  let h: Double
  let l: Double

  func stockDescription() -> String 
    return "\(name ?? "unknown") : \(c), h: \(h), l: \(l)"
  


struct StockManager 

  let decoder = JSONDecoder()
  let stockUrl = "https://finnhub.io/api/v1/quote?token=AUTH_TOKEN"

  func fetchStocks(stockNames: [String], callback: @escaping (StockData?, Error?) -> Void) 
    for name in stockNames 
      self.fetchStock(stockName: name, callback: callback)
    
  

  func fetchStock(stockName: String, callback: @escaping (StockData?, Error?) -> Void)  
    let urlString = "\(stockUrl)&symbol=\(stockName)"

    if let url = URL(string: urlString)

      let session = URLSession(configuration: .default)

      let task = session.dataTask(with: url)  (data, response, error) in

        if let data = data 
          do 
            var stockData = try decoder.decode(StockData.self, from: data)
            stockData.name = stockName
            callback(stockData, nil)
           catch let error 
            callback(nil, error)
          

         else if let error = error 
          callback(nil, error)
        
      

      task.resume()
    
  

因此,我们不使用返回值,而是传入另一个方法作为参数,并在网络调用返回时调用它。这远不是这样做的唯一方法,并且有很多关于错误处理的细节可以做得更好/不同,但正如我所说,它会起作用。我向StockData 添加了一个可变字符串字段,因为响应不包含股票名称,这有点难看。我还添加了一个stockDescription() 方法来方便地打印出数据 - 不确定你想要/需要什么。您可能更愿意将其放在 VC 上。

这是 tableview 委托方法(更简单但与原始方法没有太大区别)

  func numberOfSections(in tableView: UITableView) -> Int 
    return 1
  

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
      return stockData.count
  

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

      cell.textLabel?.text = stockData[indexPath.row].stockDescription()

      return cell
  

最后一点:发布代码以删除身份验证令牌等内容时要小心。我认为对于像 finnhub.io 这样的免费网站来说这并不重要,但它可能会导致问题。 (我已从您的原始帖子中删除了一个 - 如果您担心有人使用它,您可以在 finnhub 上轻松地重新生成它)。

【讨论】:

Rich,这是一个很好的解释,但是,我无法访问 stockData,因为我的单元格是在一个完全不同的类中初始化的。我该如何解决? 我尝试创建一个新变量将其链接到 StockData swift 文件,但是它要求我填写参数。无论我尝试什么,它都没有给我从 stockData(variable) 到 StockData(file) 的连接。有什么建议吗? 所以 VC 中的 stockData 应该是一个属性,一个类型为 StockData 的结构数组(声明:var stockData: [StockData] = [] - 为清楚起见,最好将其称为 stockDataItems。可能你从 ViewController 中遗漏了那个?(我意识到我没有明确提到它,对此感到抱歉) 哦,我明白了,现在一切顺利。但是有一个小问题,它在我的调试控制台中显示“c”的值为 nil,并且我的 UITableview 上没有显示任何内容。我该如何解决这个问题? 哦,Rich,我忘了详细告诉你我的目标。我想在单元格标题上显示我的股票名称,在右侧的详细文本标签上,我想显示当前股票价格。【参考方案2】:

实际上你的做法是错误的,api 调用需要时间,在你从 api 获得响应后,你必须根据结果更新 UI。

问题: 您的代码的问题在于,在显示单元格时,仅进行了 api 调用,甚至在获得结果之前,您的代码将显示单元格的空/默认数据。

解决方案: (1) 创建一个函数 getStocks() ,您将在其中调用所有必需股票的 api

var stockResponses = StockResponse

func getStocks()

    for stock in listOfStocks
    
       //make performRequest here and store response in the stockResponses array
       //call tableViewName.reloadData() in every response
    

(2) cellForRowAt 方法中,现在使用这个数组来显示数据

cell.textLabel?.text = listOfStocks[indexPath.row]
cell.detailTextLabel?.text = stockResponses[indexPath.row] //show whatever data you want to show from response here
return cell

希望这会在您的表格中显示数据。 p.s tableView.reloadData() - 这样做是一种不好的方法,但为了让它进入这个显示阶段,你可以使用它,但我建议使用 DispatchGroup 进行多个 api 调用

【讨论】:

以上是关于如何在 Xcode swift 的 UITableview 单元格中显示当前股票价格?的主要内容,如果未能解决你的问题,请参考以下文章

UITable 视图项有时会在选择时消失(取决于屏幕大小)

向上滚动 UITable 后的 Swift 5 调用函数

如何在代码中使用 UItable 单元中的按钮插座

使用 IOS9 时,无法在 UITable 的部分标题上的 UIButtons 上触发触摸事件(IOS10 很好)

如何在 Xcode8 上使用 Swift 2.2?

如何在 Xcode 中增加字体大小以实现 swift [重复]