-1

I am creating an app that fetches stock data from Alpha Vantage and displays it, as a first attempt at Swift (I usually develop in JS).

More precisely, I have a table view, and each cell has the following labels:

  • "Open" price label
  • "High" price label
  • "Low" price label
  • "Close" price label
  • "Volume" label

This is the custom class I'm using for the cell.

    import UIKit

    class StockChoiceCell: UITableViewCell {

        @IBOutlet weak var openPriceLabel: UILabel!
        @IBOutlet weak var highPriceLabel: UILabel!
        @IBOutlet weak var lowPriceLabel: UILabel!
        @IBOutlet weak var closePriceLabel: UILabel!
        @IBOutlet weak var volumeLabel: UILabel!

    }

I have an asynchronous function that successfully retrieves the desired data from Alpha Vantage and returns [DataPoint], where DataPoint is a struct that contains the open, high, low and close prices, as well as the volume. I say successful, as I use the function in another facet of the app without issues.

That function resembles:

func retrieveDataFromAPI (tic: String, completion: @escaping ([DataPoint])->()) {

// Fetch stuff

// Pack it under the DataPoint structure

// ...

completion(arrayOfDataPoints)

}

Here is the issue.

Due to the asynchronous nature of the function, I do not know how to make the application "wait" for the data fetching to complete before running:

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

        // ... Setting stuff, etc.

        return cell
    } 

I searched online, trying primarily the following two posts, as one of my attempts consisted of nesting the asynchronous function within another synchronous function:

If this were JS, I'd immediately use a Promise. But Swift is new to me, and I am still exploring how asynchronous functions work in the language.

Thank you!

Philip Becker
  • 21
  • 1
  • 8
  • 2
    Does `.reloadData()` on the `UITableView` work for what you are looking for? Just call it when you have all the data from your request. – George Jan 02 '20 at 00:16

2 Answers2

4

Due to the asynchronous nature of the function, I do not know how to make the application "wait" for the data fetching to complete before running cellForRowAtIndexPath

Well, cellForRowAtIndexPath will automatically "not execute", when there are no data supplied to the table view. Remember that this is a table view data source method. You don't get to call it directly. So just implement cellForRowAtIndexPath as you would normally. If numberOfRowsInSection returns 0, then cellForRowAtIndexPath would not be called, would it?

I would imagine you have implemented numberOfRowsInSection with a line like this:

return dataSource.count

where dataSource is a property of your VC, of type [DataPoint]. Initially, set this dataSource property to an empty array:

var dataSource = [DataPoint]()

And when the table view loads, it will display nothing, because the data has not been fetched. Now in viewDidLoad, we call this async function to fetch the data:

retrieveDataFromAPI(tic: "some string") {
    dataFetched in
    DispatchQueue.main.async {
        self.dataSource = dataFetched
        self.tableView.reloadData() // this calls the table view data source methods again
    }
}

At the time you call reloadData, dataSource will no longer be empty, and cellForRowAtIndexPath will be called for the appropriate index paths.


Note that there is PromiseKit for Swift, which you may or may not find familiar.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • That did the trick! Your code worked perfectly and your main point that `cellForRowAtIndexPath` is not called when the array is empty (a point also made by @Sh_Khan) was very interesting and useful to me as well. Thank you all! – Philip Becker Jan 02 '20 at 01:43
1

You can hide the table before the request and show activity indicator , then show it inside the completion success block , btw cellForRowAt won't be called whennumberOfRowsInSection returns zero ( intial count of the table data source array ) when the table loads before response returns

retrieveDataFromAPI(tic:<##>) { res in
 DispatchQueue.main.async {
    self.arr = res
    self.tableView.reloadData()
 }
}
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
  • Thank you for the replies. You and @George_E mention using `.reloadData()`, which indeed seems very promising. However, how would I set the `result` data to the labels? Do I need to use this snippet within the `tableView cellForRowAt` function? – Philip Becker Jan 02 '20 at 00:42