如何在 Xcode swift 的 UITableview 单元格中显示当前股票价格?
How can I display the current stock price in a UITableview cell in Xcode swift?
我创建了一个 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
}
}
这是我创建 table 视图和单元格的代码:
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))。如果有人对此问题有答案,请告诉我,谢谢!
你实际上做错了,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
希望这会显示您 table 中的数据。
p.s tableView.reloadData() - 以这种方式做是一个糟糕的方法,但为了能够进入这个显示阶段,你可以使用它,但我建议使用 DispatchGroup 来制作多个api 调用
所以第一个问题是你的网络调用不会像你写的那样正常工作。您编写的方法是同步的,这意味着它立即 return 一个值,而它包含的网络调用是异步的,这意味着它将 return 某种值(您想要的数据或一段时间后,使用 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
的实例变量数组(驱动 table)并在 tableView 上调用重新加载。
DispatchQueue.main.async
在那里是因为网络请求将在后台线程上 return,我们无法从那里更新 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()
}
}
}
因此,我们没有使用 return 值,而是传入另一个方法作为参数,并在网络调用返回时调用它。这远不是唯一的方法,并且有大量关于错误处理的细节可以完成很多 better/differently,但正如我所说,它会起作用。我向 StockData
添加了一个 mutable 字符串字段,因为响应不包含股票名称,这有点难看。我还添加了一个 stockDescription()
方法来方便地打印出数据 - 不确定你在这里 want/need 是什么。您可能更愿意将其放在 VC.
这是table视图委托方法(更简单但与原始方法没有太大区别)
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
}
}
最后一点:在 post 编写代码以删除诸如身份验证令牌之类的内容时要小心。我认为这对于像 finnhub.io 这样的免费网站来说并不重要,但它可能会导致问题。 (我已经从您原来的 post 中删除了一个 - 如果您担心有人使用它,您可以轻松地在 finnhub 上重新生成它)。
我创建了一个 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
}
}
这是我创建 table 视图和单元格的代码:
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))。如果有人对此问题有答案,请告诉我,谢谢!
你实际上做错了,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
希望这会显示您 table 中的数据。 p.s tableView.reloadData() - 以这种方式做是一个糟糕的方法,但为了能够进入这个显示阶段,你可以使用它,但我建议使用 DispatchGroup 来制作多个api 调用
所以第一个问题是你的网络调用不会像你写的那样正常工作。您编写的方法是同步的,这意味着它立即 return 一个值,而它包含的网络调用是异步的,这意味着它将 return 某种值(您想要的数据或一段时间后,使用 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
的实例变量数组(驱动 table)并在 tableView 上调用重新加载。
DispatchQueue.main.async
在那里是因为网络请求将在后台线程上 return,我们无法从那里更新 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()
}
}
}
因此,我们没有使用 return 值,而是传入另一个方法作为参数,并在网络调用返回时调用它。这远不是唯一的方法,并且有大量关于错误处理的细节可以完成很多 better/differently,但正如我所说,它会起作用。我向 StockData
添加了一个 mutable 字符串字段,因为响应不包含股票名称,这有点难看。我还添加了一个 stockDescription()
方法来方便地打印出数据 - 不确定你在这里 want/need 是什么。您可能更愿意将其放在 VC.
这是table视图委托方法(更简单但与原始方法没有太大区别)
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
}
}
最后一点:在 post 编写代码以删除诸如身份验证令牌之类的内容时要小心。我认为这对于像 finnhub.io 这样的免费网站来说并不重要,但它可能会导致问题。 (我已经从您原来的 post 中删除了一个 - 如果您担心有人使用它,您可以轻松地在 finnhub 上重新生成它)。