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!)")
}
}
}
Related
I am working on an small project where I have an app that takes in tvshow information entered by the user and displays it in a custom tableview cell. I would like to sort the shows as they are entered based on which current episode the user is on. I know this code works because I tested it with print statements and it sorts the array but it does not sort on the simulator. So I just was curious where I should place this so that it sorts on the app side.
func sortShows() {
let sortedShows = tvShows.sorted { $0.currentEpisode > $1.currentEpisode}
TVShowTableView.reloadData()
print(sortedShows)
}
Here is where I am currently placing it inside my view controller
extension TVShowListViewController: AddTVShowDelegate {
func tvShowWasCreated(tvShow: TVShow) {
tvShows.append(tvShow)
dismiss(animated: true, completion: nil)
TVShowTableView.reloadData()
sortShows()
}
}
In this part of your code:
func sortShows() {
// here you are creating a NEW array
let sortedShows = tvShows.sorted { $0.currentEpisode > $1.currentEpisode}
// here you tell the table view to reload with the OLD array
TVShowTableView.reloadData()
print(sortedShows)
}
In your controller class, you probably have something like:
var tvShows: [TVShow] = [TVShow]()
and then you populate it with shows, like you do with a new show:
tvShows.append(tvShow)
Then your controller is doing something like:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tvShowCell", for: indexPath) as! TVShowCell
cell.tvShow = tvShows[indexPath.row]
return cell
}
What you want to do is add another var to your class:
var sortedShows: [TVShow] = [TVShow]()
then change your sort func to use that array:
func sortShows() {
// use the existing class-level array
sortedShows = tvShows.sorted { $0.currentEpisode > $1.currentEpisode}
// here you tell the table view to reload
TVShowTableView.reloadData()
print(sortedShows)
}
and change your other funcs to use the sortedShows array:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// use sortedShows array
return sortedShows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tvShowCell", for: indexPath) as! TVShowCell
// use sortedShows array
cell.tvShow = sortedShows[indexPath.row]
return cell
}
and you'll want to call sortShows() at the end of viewDidLoad() (or wherever you are getting your initial list of shows).
Edit
Another way you might use cellForRowAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tvShowCell", for: indexPath) as! TVShowCell
// use sortedShows array
let tvShow = sortedShows[indexPath.row]
cell.showTitleLable.text = tvShow.title
cell.showDecriptionLable.text = tvShow.description
return cell
}
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 trying to combine a CollectionViewwith a TableView, so fare everything works except one problem, which I cant fix myself.
I have to load some data in the CollectionViews which are sorted with the header of the TableViewCell where the CollectionView is inside. For some reason, every time I start the app, the first three TableViewCells are identical. If I scroll a little bit vertically, they change to the right Data.
But it can also happen that while using it sometimes displays the same Data as in on TableViewCell another TableViewCell, here again the problem is solved if I scroll a little.
I think the problem are the reusableCells but I cant find the mistake myself. I tried to insert a colletionView.reloadData() and to set the cells to nil before reusing, sadly this didn`t work.
My TableViewController
import UIKit
import RealmSwift
import Alamofire
import SwiftyJSON
let myGroupLive = DispatchGroup()
let myGroupCommunity = DispatchGroup()
var channelTitle=""
class HomeVTwoTableViewController: UITableViewController {
var headers = ["LIVE","Channel1", "Channel2", "Channel3", "Channel4", "Channel5", "Channel6"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.isTranslucent = false
DataController().fetchDataLive(mode: "get")
DataController().fetchDataCommunity(mode: "get")
}
//MARK: Custom Tableview Headers
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return headers[section]
}
//MARK: DataSource Methods
override func numberOfSections(in tableView: UITableView) -> Int {
return headers.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
//Choosing the responsible PrototypCell for the Sections
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellBig", for: indexPath) as! HomeVTwoTableViewCell
print("TableViewreloadMain")
cell.collectionView.reloadData()
return cell
}
else if indexPath.section >= 1 {
// getting header Titel for reuse in cell
channelTitle = self.tableView(tableView, titleForHeaderInSection: indexPath.section)!
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
// anti Duplicate protection
cell.collectionView.reloadData()
return cell
}
else {
channelTitle = self.tableView(tableView, titleForHeaderInSection: indexPath.section)!
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
// anti Duplicate protection
cell.collectionView.reloadData()
return cell
}
}
}
}
My TableViewCell with `CollectionView
import UIKit
import RealmSwift
var communities: Results<Community>?
class HomeVTwoTableViewCellSmall: UITableViewCell{
//serves as a translator from ChannelName to the ChannelId
var channelOverview: [String:String] = ["Channel1": "399", "Channel2": "401", "Channel3": "360", "Channel4": "322", "Channel5": "385", "Channel6": "4"]
//Initiaize the CellChannel Container
var cellChannel: Results<Community>!
//Initialize the translated ChannelId
var channelId: String = ""
#IBOutlet weak var collectionView: UICollectionView!
}
extension HomeVTwoTableViewCellSmall: UICollectionViewDataSource,UICollectionViewDelegate {
//MARK: Datasource Methods
func numberOfSections(in collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return (cellChannel.count)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCellSmall", for: indexPath) as? HomeVTwoCollectionViewCellSmall else
{
fatalError("Cell has wrong type")
}
//removes the old image and Titel
cell.imageView.image = nil
cell.titleLbl.text = nil
//inserting the channel specific data
let url : String = (cellChannel[indexPath.row].pictureId)
let name :String = (cellChannel[indexPath.row].communityName)
cell.titleLbl.text = name
cell.imageView.downloadedFrom(link :"link")
return cell
}
//MARK: Delegate Methods
override func layoutSubviews() {
myGroupCommunity.notify(queue: DispatchQueue.main, execute: {
let realm = try! Realm()
//Getting the ChannelId from Dictionary
self.channelId = self.channelOverview[channelTitle]!
//load data from Realm into variables
self.cellChannel = realm.objects(Community.self).filter("channelId = \(String(describing: self.channelId)) ")
self.collectionView.dataSource = self
self.collectionView.delegate = self
print("collectionView layout Subviews")
self.collectionView.reloadData()
})
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedCommunity = (cellChannel[indexPath.row].communityId)
let home = HomeViewController()
home.showCommunityDetail()
}
}
Thanks in advance.
tl;dr make channelTitle a variable on your cell and not a global variable. Also, clear it, and your other cell variables, on prepareForReuse
I may be mistaken here, but are you setting the channelTitle on the cells once you create them? As I see it, in your viewController you create cells based on your headers, and for each cell you set TableViewController's channelTitle to be the title at the given section.
If this is the case, then the TableViewCell actually isn't receiving any information about what it should be loading before you call reloadData().
In general, I would also recommend implementing prepareForReuse in your HomeVTwoTableViewCellSmall, since it will give you a chance to clean up any stale data. Likely you would want to do something like set cellChannel and channelId to empty strings or nil in that method, so when the cell is reused that old data is sticking around.
ALSO, I just reread the cell code you have, and it looks like you're doing some critical initial cell setup in layoutSubviews. That method is going to be potentially called a lot, but you really only need it to be called once (for the majority of what it does). Try this out:
override the init with reuse identifier on the cell
in that init, add self.collectionView.dataSource = self and self.collectionView.delegate = self
add a didSet on channelTitle
set channelTitle in the viewController
So the code would look like:
var channelTitle: String = "" {
didSet {
self.channelId = self.channelOverview[channelTitle]!
self.cellChannel = realm.objects(Community.self).filter("channelId = \(String(describing: self.channelId)) ")
self.collectionView.reloadData()
}
}
This way you're only reloading your data when the cell is updated with a new channel, rather than every layout of the cell's views.
Sorry... one more addition. I wasn't aware of how your channelTitle was actually being passed. As I see it, you're using channelTitle as a global variable rather than a local one. Don't do that! remove channelTitle from where it is currently before implementing the code above. You'll see some errors, because you're setting it in the ViewController and accessing it in the cell. What you want is to set the channelTitle on the cell from the ViewController (as I outlined above). That also explains why you were seeing the same data across all three cells. Basically you had set only ONE channelTitle and all three cells were looking to that global value to fetch their data.
Hope that helps a little!
(also, you should be able to remove your else if block in the cellForRowAtIndexPath method, since the else block that follows it covers the same code. You can also delete your viewDidLoad, since it isn't doing anything, and you should, as a rule, see if you can get rid of any !'s because they're unsafe. Use ? or guard or if let instead)
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