Reputation: 1451
I have a table view with custom cells. They are quite tall, so only one cell is completely visible on the screen and maybe, depending on the position of that cell, the top 25% of the second one. These cells represent dummy items, which have names. Inside of each cell there is a button. When tapped for the first time, it shows a small UIView
inside the cell and adds the item to an array, and being tapped for the second time, hides it and removes the item. The part of adding and removing items works fine, however, there is a problem related to showing and hiding views because of the fact that cells are reused in a UITableView
When I add the view, for example, on the first cell, on the third or fourth cell (after the cell is reused) I can still see that view.
To prevent this I've tried to loop the array of items and check their names against each cell's name label's text. I know that this method is not very efficient (what if there are thousands of them?), but I've tried it anyway.
Here is the simple code for it (checkedItems
is the array of items, for which the view should be visible):
if let cell = cell as? ItemTableViewCell {
if cell.itemNameLabel.text != nil {
for item in checkedItems {
if cell.itemNameLabel.text == item.name {
cell.checkedView.isHidden = false
} else {
cell.checkedView.isHidden = true
}
}
}
This code works fine at a first glance, but after digging a bit deeper some issues show up. When I tap on the first cell to show the view, and then I tap on the second one to show the view on it, too, it works fine. However, when I tap, for example, on the first one and the third one, the view on the first cell disappears, but the item is still in the array. I suspect, that the reason is still the fact of cells being reused because, again, cells are quite big in their height so the first cell is not visible when the third one is. I've tried to use the code above inside tableView(_:,cellForRow:)
and tableView(_:,willDisplay:,forRowAt:)
methods but the result is the same.
So, here is the problem: I need to find an EFFICIENT way to check cells and show the view ONLY inside of those which items are in the checkedItems
array.
EDITED Here is how the cell looks with and without the view (the purple circle is the button, and the view is the orange one)
And here is the code for the button:
protocol ItemTableViewCellDelegate: class {
func cellCheckButtonDidTapped(cell: ExampleTableViewCell)
}
Inside the cell:
@IBAction func checkButtonTapped(_ sender: UIButton) {
delegate?.cellCheckButtonDidTapped(cell: self)
}
Inside the view controller (NOTE: the code here just shows and hides the view. The purpose of the code is to show how the button interacts with the table view
):
extension ItemCellsTableViewController: ItemTableViewCellDelegate {
func cellCheckButtonDidTapped(cell: ItemTableViewCell) {
UIView.transition(with: cell.checkedView, duration: 0.1, options: .transitionCrossDissolve, animations: {
cell.checkedView.isHidden = !cell.checkedView.isHidden
}, completion: nil)
}
EDITED 2
Here is the full code of tableView(_ cellForRowAt:)
method (I've deleted the looping part from the question to make it clear what was the method initially doing). The item
property on the cell just sets the name of the item (itemNameLabel
's text
).
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier:
ItemTableViewCell.identifier, for: indexPath) as? ItemTableViewCell{
cell.item = items[indexPath.row]
cell.delegate = self
cell.selectionStyle = .none
return cell
}
return UITableViewCell()
}
I've tried the solution, suggested here, but this doesn't work for me.
If you have faced with such a problem and know how to solve it, I would appreciate your help and suggestions very much.
Upvotes: 0
Views: 1533
Reputation: 6876
I would keep the state of your individual cells as part of the modeldata that lies behind every cell.
I assume that you have an array of model objects that you use when populating you tableview in tableView(_:,cellForRow:)
. That model is populated from some backend service that gives you some JSON, which you then map to model objects once the view is loaded the first time.
If you add a property to your model objects indicating whether the cell has been pressed or not, you can use that when you populate your cell.
You should probably create a "wrapper object" containing your original JSON data and then a variable containing the state, lets call it isHidden
. You can either use a Bool
value or you can use an enum
if you're up for it. Here is an example using just a Bool
struct MyWrappedModel {
var yourJSONDataHere: YourModelType
var isHidden = true
init(yourJSONModel: YourModelType) {
self.yourJSONDataHere = yourJSONModel
}
}
In any case, when your cell is tapped (in didSelectRow
) you would:
MyWrappedModel
object in your array of wrapped modeldata objects based on the indexpathisHidden
value on thatreloadRows(at:with:)
In tableView(_:,cellForRow:)
you can now check if isHidden
and do some rendering based on that:
...//fetch the modelObject for the current IndexPath
cell.checkedView.isHidden = modelObject.isHidden
Futhermore, know that the method prepareForReuse
exists on a UITableViewCell
. This method is called when ever a cell is just about to be recycled. That means that you can use that as a last resort to "initialize" your table view cells before they are rendered. So in your case you could hide the checkedView
as a default.
If you do this, you no longer have to use an array to keep track of which cells have been tapped. The modeldata it self knows what state it holds and is completely independent of cell positions and recycling.
Hope this helps.
Upvotes: 0
Reputation: 4552
Try this.
Define Globally : var arrIndexPaths = NSMutableArray()
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 30
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TableViewCell = self.tblVW.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.textLabel?.text = String.init(format: "Row %d", indexPath.row)
cell.btn.tag = indexPath.row
cell.btn.addTarget(self, action: #selector(btnTapped), for: .touchUpInside)
if arrIndexPaths.contains(indexPath) {
cell.backgroundColor = UIColor.red.withAlphaComponent(0.2)
}
else {
cell.backgroundColor = UIColor.white
}
return cell;
}
@IBAction func btnTapped(_ sender: UIButton) {
let selectedIndexPath = NSIndexPath.init(row: sender.tag, section: 0)
// IF YOU WANT TO SHOW SINGLE SELECTED VIEW AT A TIME THAN TRY THIS
arrIndexPaths.removeAllObjects()
arrIndexPaths.add(selectedIndexPath)
self.tblVW.reloadData()
}
Upvotes: 1