I have a table that displays the results of an http request.
Sometimes the application crashes because the table is displayed before the query is finished .. How can the application no crash?
The table :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! MyCustomCell
for i in 0...nameOfRoutesStart.count {
if (indexPath.row == i) {
cell.originLabel.text = nameOfRoutesStart[i]
cell.destinationLabel.text = nameOfRoutesEnd[i]
let id = driver[i]
self.userTasks.user(driverId: id, completionHandler: { (status, success) -> Void in
if success {
cell.driverLabel.text = self.userTasks.username
}
})
cell.textLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
}
}
return cell
}
I make the request in another function
Thanks
Extract your asynchronous code from the tableview methods to your viewDidLoad. Save the data you retrieve in a variable declared in your controller and call .reloadData() on your tableview when your fetch is finished.
Related
I am trying to display the result of my request from an api into a cell.I am able to make the request and parse the data. But when I try to display the content in a cell and print the cell value, the result is optional (“”).Can someone explain me why .
cell.restaurantNameLabel.text = apiDataModel.restaurantName
apiDataModel.restaurantName is not nil
Any help is appreciated ! Thanks
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! DiscoverTableViewCell
cell.discoverImage.image = UIImage(named: "Detail")
cell.restaurantNameLabel.text = apiDataModel.restaurantName
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let currentCell = tableView.cellForRow(at: indexPath) as! DiscoverTableViewCell
print(currentCell.restaurantNameLabel.text)
}
func search(url: String, parameters : [String:String]) {
let headers: HTTPHeaders = ["Authorization":"Bearer \(apiKey)"]
Alamofire.request(url, method: .get, parameters: parameters, headers: headers ) .responseJSON{
URLResponse in
//print(URLResponse)
if URLResponse.result.isSuccess {
let yelpDataJSON = JSON(URLResponse.value!)
self.updateYelpData(Json: yelpDataJSON)
print("\(yelpDataJSON)")
}else{
print("error")
}
}
}
func updateYelpData(Json : JSON){
if let nameJSON = Json["businesses"][0]["name"].string {
apiDataModel.restaurantName = nameJSON
print(apiDataModel.restaurantName)
apiDataModel.restaurantLocation = Json["businesses"][0]["location"]["display_address"][0].stringValue
}else{
print("error")
}
}
You’re not showing us where you called search, but request is an asynchronous method, but you never call reloadData on your table view inside updateYelpData. Thus the initial population of the table view is happening before the data has been retrieved and parsed by Alamofire.
If you put a tableView.reloadData() inside updateYelpData, the table view will be update with real data after it is retrieved by Alamofire.
It appears apiDataModel.restaurantName is nil / "" and setting that to the label text will make it ""
You need to reload the table after
self.updateYelpData(Json: yelpDataJSON)
self.tableView.reloadData()
and make sure you have a valid result
I’m creating a Tableview and trying to include in the cell one information that I receive through a JSON from an API.
The information (JSON) is being received very well and recorded correctly inside the variable.
However, what I could find is that as the information is received with a small “delay” is not being set as the cell’s label text at the cell creation moment, which is being set with the default variable content.
I guess the solution would be to update the label at the moment I parse the JSON content, right? How do I do this? (Update a cell’s label after it is already created)
Any other insight/ solution is greatly appreciated.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, AddStock {
let operacoesAcoes = ListaOperacoes()
var todasAsOperacoes : [Operacao] = []
#IBOutlet weak var acoesTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
acoesTableView.delegate = self
acoesTableView.dataSource = self
acoesTableView.register(UINib(nibName: "StandardStockListCell", bundle: nil), forCellReuseIdentifier: "standardCell")
operacoesAcoes.consolidaAcoes()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return operacoesAcoes.carteiraAcoes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "standardCell", for: indexPath) as! StandardStockListCell
let informacaoCompletaAcao = operacoesAcoes.carteiraAcoes[indexPath.row]
cell.codigoTextField.text = informacaoCompletaAcao.codigoAcao
cell.nomeTextField.text = informacaoCompletaAcao.nomeAcao
cell.quantidadeTotal.text = String(informacaoCompletaAcao.quantidadeTotal)
cell.precoMedioLabel.text = String(format: "%.2f", informacaoCompletaAcao.precoMedio)
//
// This is the part of the code that should set one label with a value returned from "buscaCotacao" but it does not work
// because at the time the cell is displayed it is still not updated from JSON information:
// Note: the buscaCotacao func is working fine
cell.precoAtualLabel.text = buscaCotacao(ativo: informacaoCompletaAcao.codigoAcao)
return cell
}
You need to reload the table view on the main thread after receiving and parsing the JSON.
self.acoesTableView.reloadData()
I did some research and tryouts, and could figure out a very simple (and now obvious) solution to update my Label after the result of the request is received:
- I call the function which retrieves the information from an API to update the cell ("buscaCotacao"), including the [cell row] information
- I update the cell's label from inside the function, which will happen only after the reply is received:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "standardCell", for: indexPath) as! StandardStockListCell
let informacaoCompletaAcao = operacoesAcoes.carteiraAcoes[indexPath.row]
cell.codigoTextField.text = informacaoCompletaAcao.codigoAcao
cell.nomeTextField.text = informacaoCompletaAcao.nomeAcao
cell.quantidadeTotal.text = String(informacaoCompletaAcao.quantidadeTotal)
cell.precoMedioLabel.text = "R$ "+String(format: "%.2f", informacaoCompletaAcao.precoMedio)
buscaCotacao(ativo: informacaoCompletaAcao.codigoAcao, for: cell, indexPath: indexPath)
return cell
}
And in the function:
func buscaCotacao (ativo: String, for cell: StandardStockListCell, indexPath: IndexPath) {
let finalURL = "https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&interval=1min&apikey=XXXXXXXXXXXXXX&outputsize=compact&symbol=" + ativo
Alamofire.request(finalURL, method: .get)
.responseJSON { response in
if response.result.isSuccess {
let resultadoJSON : JSON = JSON(response.result.value!)
let resultado = Float (self.parseResultado(json: resultadoJSON))!
cell.precoAtualLabel.text = "R$ "+String(format: "%.2f", resultado)
self.cotacoes[ativo] = resultado
} else {
cell.precoAtualLabel.text = "N/D"
print("Error: \(response.result.error!)")
}
}
}
I think this has been talked about quite a bit here and people are working towards passing items from background to main threads.
My question is simple. I have a large list in a UITableView when loading the list the UI for 1-2 seconds. What is the best workaround to prevent this at this point (Ideally without loading the whole fetched data in the memory)?
func bindTableView(term: String? = nil, segment: String? = nil) {
resultsBag = DisposeBag()
if let p = searchPredicateOrganisations(term) , segmentControl.selectedSegmentIndex == 1 {
organisationsResults = realm.objects(Organisation.self).filter(p)
sectionTitles = Set(organisationsResults.value(forKeyPath: "sectionKey") as! [String]).sorted()
Observable.changesetFrom(organisationsResults)
.subscribe(onNext: {organisationResults, changes in
self.processChanges(changes: changes)
}).addDisposableTo(resultsBag)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
if let i = individualsResults {
cell.individual = i.filter("sectionKey == %#", sectionTitles[indexPath.section])[indexPath.row]
}
return cell
}
Thank you in advance.
I have encountered an error in swift when attempting to create a tableview made up of custom cells dependent upon a set of conditions.
Here is my code:
var tableData: [String] = []
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
// number of rows in table view
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData.count
}
// create a cell for each table view row
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let phonenocell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier("phonecell", forIndexPath: indexPath) as! MyCustomCell
let pincell:SocialCell = self.tableView.dequeueReusableCellWithIdentifier("socialcell", forIndexPath: indexPath) as! SocialCell
let fbcell:FacebookCell = self.tableView.dequeueReusableCellWithIdentifier("facebookcell", forIndexPath: indexPath) as! FacebookCell
let snapcell:SnapchatCell = self.tableView.dequeueReusableCellWithIdentifier("snapchatcell", forIndexPath: indexPath) as! SnapchatCell
let twitcell:TwitterCell = self.tableView.dequeueReusableCellWithIdentifier("twittercell", forIndexPath: indexPath) as! TwitterCell
let instacell:InstagramCell = self.tableView.dequeueReusableCellWithIdentifier("instagramcell", forIndexPath: indexPath) as! InstagramCell
if tableData.contains("Number") {
return phonenocell
}
if tableData.contains("Social") {
return pincell
}
if tableData.contains("Facebook") {
return fbcell
}
if tableData.contains("Snapchat") {
return snapcell
}
if tableData.contains("Twitter") {
return twitcell
}
if tableData.contains("Instagram") {
return instacell
}
}
When attempting to build and run I get a build failed with the following fault:
"Missing Return in a function expected to return 'UITableViewCell'
I have been over and over my code but I honestly cannot see where I am going wrong...
Any help would be greatly appreciated!
You need to return cell for sure.
You already do in conditions, but in case none of your condition statements would success, your return call wouldn't be fired.
Appending, for example:
return phonenocell
to the end of the function, should be quick fix for your code. It ensures, that the function will return a cell (that is mandatory).
My data source is the array tableData. This is constructed on the previous view as: #IBAction func switch1Toggled(sender: UISwitch) { if mySwitch1.on { fbTextBox.text = "Selected" dataArray.append("Facebook")
And this may be the main issue:
Assuming, that you choose 'facebook' and that you reload your tableView, every row will pass the first condition as it IS contained.
You should put this in your method:
//assuming your data source contains multiple members, and your numberOfRowsInSections... method return tableData.count, you need to get each item for each row:
let currentTag = tableData[indexPath.row]
if (currentTag == "something") { //e.g. Facebook
let somethingcell:MySomethingCell = ...
self.tableView.dequeueReusableCellWithIdentifier("somethingcell", forIndexPath: indexPath) as! MySomethingCell
return somethingcell
} else if {
...
}
return emptycell //this line is just for the case, when no of your conditions will pass and you don't catch all the situations...
maybe your array elements doesn't match the condition, it's better to return default value instead of ur conditions failed
In my code I have a search bar that when its search button is clicked, it triggers this function here:
func getStocks(ticker: String) {
do {
try Stocks.getStocks(ticker, completion: {stockList in
self.listOfStocks = stockList
print("Stock item is: \n", self.listOfStocks.popLast())
dispatch_async(dispatch_get_main_queue(), {
self.saveStocks(self.listOfStocks.popLast()!)
self.tableView.reloadData()
})
})
} catch {
print("Failed to get stocks")
}
}
The purpose of this function is to go through my API call, get data for the item the user has specified in the search bar, append it to a global list of items while also saving the most recent item in the global list into Core Data. Later on I have a block of code that sets the text cell label and sets it to the name property of my Stock struct:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("stockItem", forIndexPath: indexPath)
if let label:UILabel = cell.textLabel {
label.text = self.listOfStocks[indexPath.row].name
}
return cell
}
I've checked to make sure the reuse identifier is correct so that wouldn't be the issue.
You first need to track down where in your code is the issue. I would follow these steps to do that.
Confirm that your Stocks.getStocks() static function is working correctly and that the api call is returning valid data. You have not supplied code for this.
Check that your data source, in this case self.listOfStocks is being populated with the data from the API call. Set a breakpoint or use a print statement in the getStocks() method.
`
func getStocks(ticker: String) {
do {
try Stocks.getStocks(ticker, completion: {stockList in
if let list = stockList {
self.listOfStocks = list
dispatch_async(dispatch_get_main_queue(), {
if let last = self.listOfStocks.popLast() {
self.saveStocks(last)
}
self.tableView.reloadData()
})
} else {
print("ERROR: stockList is nil!")
}
})
} catch {
print("Failed to get stocks")
}
}
Review your table view delegate and dataSource delegate methods are correctly setup. Below is how I would check my cellForRowAtIndexPath method.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("stockItem", forIndexPath: indexPath)
if let datasource = self.listOfStocks[indexPath.row] {
textLabel.text = datSource.name
} else {
textLabel.text = "Row \(indexPath.row): NOT set!"
}
return cell
}