I would like to load a tableview with a combination of synchronous and asynchronous (remote) data. The synchronous data loads immediately. How can I get the asynchronous data to load when it is ready? Do I put something in cellforRow or in viewwill appear?
Right now I set the label value in the tableview cell but the data is not updating
Here is my cellforRow code:
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
let row = indexPath.row
cell.textLabel?.text = Places[row].name
Utilities.shared.getWeather(query: Places[row].name as NSString) { (response1) in
print(response1)
DispatchQueue.main.async {
cell.detailTextLabel?.text = response1
}
}
return cell
}
The current behavior loads the data every scroll , and in worst case loads the same data many times at the same instant , so you need to keep it once by putting that logic inside your model to grab the required content and update it's content with it , then reload the table / indexPath an example is described Here
Related
I have a table view. First, I load text data for all cells from database. Next, I load image for every cell from another database. But when I tried to set images, they are not displayed.
If I use
let cell = myTableView.dataSource?.tableView(myTableView,
cellForRowAt: indexPath) as? MyTableViewCell
then images loaded only in invisible cells. It means that those cells that I see the very first (before scrolling down) don't show their images. But all others do.
Another way, if I use
let cell = myTableView.cellForRow(at: indexPath) as? MyTableViewCell
then the opposite happens - only those cells that I see show their images, but all others don't.
After that I do
cell.setImage(image: image)
func setImage(image: UIImage) {
guard let myImageView = myImageView else { return }
myImageView.image = image
}
I set images not in func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {...} because as I said, images come after creating cells.
You need to refresh the Table View in the main thread after the load. You can do it with:
DispatchQueue.main.async {
self.yourTableView.reloadData()
}
This question already has answers here:
UITableView Duplicate cells (custom cells with textfields)
(2 answers)
Closed 3 years ago.
I've created an UITableView based "count-down" timer app, each cell has a "countdown label" and a "start running button"
The issue is when I press the button, trigger the timer to run,
then I scroll down, the 10th cell gets trigger too.
when I scroll back, the first cell timer gets reset.
After searched it might be the dequeueReusableCell part,
but I don't know how to fix it.
all code on github:
https://github.com/evancohi/CountdownTimer
Here is how my cellForRowAt method setup
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "timerListCellId", for: indexPath) as! TimerListCell
cell.timerLabel.text = self.timerArray[indexPath.row]
cell.missionLabel.text = self.missionArray[indexPath.row]
return cell
}
Can you please give some advices?
UITableview's dequeueReusableCell is used to reuse the tableview cells for smooth scrolling experience.
So in order to stop you cells from duplicating, you should implement a conditional check in cellForRowAt() method implementation so that you only cells where you have started the timer perform the timer task.
eg.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "timerListCellId", for: indexPath) as! TimerListCell
cell.timerLabel.text = self.timerArray[indexPath.row]
cell.missionLabel.text = self.missionArray[indexPath.row]
cell.isTimerRunning = self.timerArray[indexPath.row]
if cell.isTimerRunning == true {
// enable trigger
}
else{
// disable trigger
}
return cell
}
In above example I have created timerArray which by default contains all 'false' entries. When you start timer for a cell, replace the entry at the same index path.row to 'true' and use this array in cellforRow, for identifying the cell where you have triggered the timer.
I have tableView with custom cell. I load some information from the web services and add it to the table if user scroll table down. Problem: after loading information in cells data change while scrolling. There is my code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.section == ProductDetailSectionId.ProductDetailView.rawValue) {
if(self.productDetailModel != nil){
if let cell = tableView.dequeueReusableCell(withIdentifier: "type1", for: indexPath) as? ProductDetailViewCell {
cell.delegate = self
cell.configureProductDetail(model: self.productDetailModel!)
return cell
}
}
}
}
If you don't want to reuse cells you can do it like this :
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "CellReuseIdentifier")
However this is very bad practice and will lead to crashes depending on how big the data used
if i were you i'd just maintain the current way but make sure on setting the data on cell on completion of download that it's the right cell
I was trying to create a custom TableView Cell in Storyboards. The correct data shows when the ViewController is loaded. However, when I start scrolling back and forth, the data is showing up in random cells, not in the right place. I was also trying to make the text of a Right Detail cell bold programmatically like this:
if dayOfWeek!-1 == indexPath.row {
cell.textLabel?.font = UIFont.boldSystemFont(ofSize: (cell.textLabel?.font.pointSize)!)
cell.detailTextLabel?.font = UIFont.boldSystemFont(ofSize: (cell.detailTextLabel?.font.pointSize)!)
}
However, similar things happened: the bold seems right at first and the text are bolded in random cells. Any idea how to fix this? Thanks!
class CustomTableViewCell: UITableViewCell {
override func prepareForReuse() {
// your cleanup code
}
}
in cell for indexpath
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomTableViewCell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! CustomTableViewCell
if dayOfWeek!-1 == indexPath.row {
cell.textLabel?.font = UIFont.boldSystemFont(ofSize: (cell.textLabel?.font.pointSize)!)
cell.detailTextLabel?.font = UIFont.boldSystemFont(ofSize: (cell.detailTextLabel?.font.pointSize)!)
}
return cell
}
You have to reload the tableview when you have called it.
The random shown data is due to the property undertaken by the various cell from the each other.
You have faced these type of problem when you are using the multiple tableview on the single view.
Hence the cell having the elements wrongly considered the properties, which can be solved by refreshing the table view which is "RELOAD".
If the Data coming is from the API , then it would be an advantage, put the Response data to your main array and Reload the table view .
I got NSData from Core Data, and when I transform from NSData to UIImage, it make my UITableView very slow to scroll. I already resize my Image before I store image to Core Data. Is there good way to do that. At first, I want to use Kingfisher, but it seems don't have this kind of method to achieve that.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "ListTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? ListTableViewCell else {
fatalError("The dequeued cell is not an instance of MealTableViewCell.")
}
//cell.imageContentPhoto.kf.setImage(with: url)
cell.imageContentPhoto.image = UIImage(data: photos[indexPath.row] as Data)//photo is NSData, I got it from Core Data
return cell
}
Your problem isn't that the transform is slow, but that it's done a lot of times when you scroll.
In order to optimize UITable, iOS does not create n cells for n rows. It creates x+2 cells (IIRC), where x is the number of visible cells.
When cellForRowAt is called, you call dequeueReusableCell, which takes one of the free cells and assigns it to the row. That way, when you scroll, there are no initializations of objects happening, and slowing the scrolling down.
The problem with your code (which is now evident), is that after the cell is allocated to the row, you again convert the image and initialize it.
What you should do is initialize the array of images in advance, for example in the viewDidLoad. Then your code would look like:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "ListTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? ListTableViewCell else {
fatalError("The dequeued cell is not an instance of MealTableViewCell.")
}
//cell.imageContentPhoto.kf.setImage(with: url)
cell.imageContentPhoto.image = imagesArray[indexPath.row] // or something similar
return cell
}
Of course, if you have a lot of different images, it might be worth doing some kind of lazy loading. Keep a place holder image while scrolling and when it stops, load the relevant images only. This is of course a lot more complicated and left as an exercise to the student.