I have a image inside my tableview cells that turns black when the cell is selected as well as the default cell turning gray. Problem is when I scroll down the screen and scroll up again the images are turn back as though they are not selected being image "cellSelected". So, after scrolling selected cells should have the image "cellSelected" yet they have "cellNotSelected". How can I make sure my app remembers the selected cells images?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! CustomerSelectsBusinessesCell
cell.selectedCell.image = UIImage(named: "cellSelected")
updateCount()
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! CustomerSelectsBusinessesCell
cell.selectedCell.image = UIImage(named: "cellNotSelected")
updateCount()
}
You need to save the change of state in your data model somewhere, and then edit your data source methods (specifically cellForRowAtindexPath) to show the cell in its new selected state.
This is true of any change you make to a cell. If the cell scrolls off-screen, it gets recycled and any customizations you've made to it's views will not longer be associated with that indexPath any more. So when the user takes action on a cell, you always need to record the information in your data model.
Related
I have a collectionView within a tableview and I am trying to access the collection view's indexPath in order to get its contents upon selecting it.
Here is my code:
TableView
//This grabs the indexPath of the collectionView Selected with its contents
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedDeal = dealArray[indexPath.row]
}
ViewController
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let cell = tableView.dequeueReusableCell(withIdentifier: "SpecialPicTableViewCell", for: indexPath) as! SpecialPicTableViewCell
//Defines the selected
let deal = cell.selectedDeal
//Grabs selected information here
dealSelected = deal.title ?? ""
//Then segues
performSegue(withIdentifier: "goToDeal", sender: nil)
tableView.deselectRow(at: indexPath, animated: true)
}
However, the selection does not register with the TableView. Nothing happens as a result.
The only information in the collection view is information you put there. You shouldn't need to get any information out of the collection view cell because you already have it.
The code you posted dequeues (possibly creates) a cell, cleans it up and gets it ready for use (possibly reuse.) That cell that you essentially created will know nothing about the data that is in the cell that was selected.
What you should be doing instead, is referring to the array where you store the deals to display and figuring out what deal is at the selected index path.
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.
In my UITableViewCell have button AddToCart buttons. As if my UITableView data is more than 10 means I have to scroll to see all data. So now if I will on first button of first UITableViewCell as I scroll down to see all records of tableview than automatically last or second last button will also click I am unable to find the problem why this is happening
I am implementing first time this type of functionality so got stuck to resolve the problem
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 13
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tblCell") as! ProductTableViewCell
cell.btnAddToCart.tag = indexPath.row
cell.btnAddToCart.addTarget(self, action: #selector(addToCartDell(sender:)), for: .touchUpInside)
return cell
}
This function is used for hide and show Add To Cart button option.
#objc func addToCartDell(sender: UIButton) {
let tagVal = sender.tag
let indexPath = IndexPath(item: tagVal, section: 0)
if let cell = tblProduct.cellForRow(at: indexPath) as? ProductTableViewCell {
cell.btnAddToCart.isHidden = true
}
}
Cells are reused. You don't save the hidden state of the cell so when a cell is reused the latest state is preserved.
In Swift the most efficient and reliable solution is to save the state added to cart in the data model and use a callback closure to update the UI in cellForRow.
In the data model add a property addedToCart, it's assumed that a custom struct or class is used as data model
var addedToCart = false
In ProductTableViewCell add the callback variable and an IBAction. Connect the IBAction to the button
var callback : (()->())?
#IBAction func buttonPressed(_ sender : UIButton) {
callback?()
}
In the controller in cellForRow handle the callback, products represents the data source array
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tblCell") as! ProductTableViewCell
let product = products[indexPath.row]
cell.btnAddToCart.isHidden = product.addedToCart
cell.callback = {
product.addedToCart = true
cell.btnAddToCart.isHidden = true
}
return cell
}
No tags, no target/action, no protocols, no extra work in willDisplayCell .
This issue is turning up because we re-user same cell for displaying any further rows that was not visible yet.
you may implement this method to correct the display of any further cells
optional func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath)
Customize the cell as you want it to appear in this delegate method. This delegate method is called just before the cell is displayed, so you can do any customization here and it will turn up in the UI as per your customization.
If we go deep in to implementation.
There must be a model that keeps the state addedToCart in this model on basis of the button tapped in a particular row and use this same model's addedToCart (model.addedToCart) to show hide the button in delegate method.
UITableView allows you to assign an accessory -- like a button -- that shows on the right side of the cell (documentation).
In my audio app, I will want to allow a user to download by clicking the accessory and then show it succeeded with a checkmark. To test this now, I just want the accessory detail to turn into a checkmark after it's tapped.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = TableData[indexPath.row]
// this works, adds a button to right cell
cell.accessoryType = .detailDisclosureButton
return cell
}
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
// doSomethingWithItem(indexPath.row)
// my logic: this function applies when button is tapped. I try to reset the accessory type to checkmark when clicked
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.accessoryType = .checkmark
}
This will build, but it results in a failure starting with *** Assertion failure in -[UITableView _dequeueReusableCellWithIdentifier:forIndexPath:usingPresentationValues:],
I'm new to Swift, but this seemed logical: set a function to take an action when the accessory is clicked and then set the accessory to a checkmark (one of the accessory types).
Is there a better way to do this?
A note: I do not want to use a segue or move to a different VC; I'd like this to update the UI within the table view.
Instead of using dequeReusableCell you should be using cellForRow in your accessoryButtonTappedForRow function.
Read the documentation to see the difference between those two functions and look a little further into how cell reuse works. cellForRow(at:) returns the specific UITableViewCell, which is what you are looking for in this situation.
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
// doSomethingWithItem(indexPath.row)
// cellForRow instead of dequeue
let cell = tableView.cellForRow(at: indexPath)
cell.accessoryType = .checkmark
}
In addition, make sure you are registering your cell with that same reuse identifier of "cell" with either registerClass or registerNib