Tableview with custom cells gets duplicate while scrolling [duplicate] - ios

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.

Related

Swift UITableView Cell changes image when scrolled

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.

As UITableviewCell count more than 10 and if I click on top of button it automatically click in last of UITableViewCell button. any idea?

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.

Turn an accessory button into a different accessory after a tap from UITableView

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

How do you get the indexPath.section and indexPath.row when a button in a cell is tapped? [duplicate]

This question already has answers here:
Issue Detecting Button cellForRowAt
(3 answers)
Closed 5 years ago.
I have a tableview divide in section, I must to print the number of row and section when I tap the button in a cell. How can I get this?
Store the indexPath inside your cell and retreive it when the button is pressed.
Table view controller
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "custom") as! CustomTableCell
cell.indexPath = indexPath
return cell
}
Custom table cell
class CustomTableCell: UITableViewCell {
var indexPath: IndexPath!
#IBAction func buttonPressed() {
print(indexPath)
}
}
Implement the tableView:didSelectRowAtIndexPath: method. When it gets called, an NSIndexPath will be passed in that has the section and row properties on it.
This method will be called every time a tableview cell is pressed. The index path will be the index path of the cell that was pressed.
https://developer.apple.com/documentation/uikit/uitableviewdelegate/1614877-tableview
EDIT:
I misread the question. When the button is pressed, get the TableViewCell from the button by accessing the superview of the button. Then use the TableView's indexPathForCell method to get the indexPath of the button.

Test a value contained in the cell of a TableView against an array

I need to test if the value of a custom cell label is contained within an array. To achieve this I created a function that does the test and returns a boolean. My problem is that every time the cell enters the screen, the test is done again.
Where should I make the test so it is done only once ?
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as CustomTableViewCell
var name:String = Array(self.contactList[indexPath.row].keys)[0]
var phones:String = self.contactList[indexPath.row]["\(name)"]!
cell.nameLabel.text = name
cell.phoneLabel.text = phones
cell.inviteButton.tag = indexPath.row
var (matched, id) = self.isRegistered(phones)
if(matched){
cell.phoneLabel.text = "TRUE"
cell.idUser = "\(id)"
}
return cell
}
I need to be able to modify the cell in response to this test. Thanks for your help !
Your test is called every time the cell appears because you a placed the test call inside cellForRowAtIndexPath: (If like you said you need to change the cell's label value according to the test result, I don't see any other choice)
You can try using the UITableViewDelegate methods
optional func tableView(_ tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
This will be called every time a cell needs to be presented.
Second way is a bit tricky and I don't recommend it, you can create a pool (array) of UITableViewCells and maintain (call test & change values) from that pool at a time you think is best (when scrolling the tableView for example)

Resources