I’m working on a shopping app project where customers can browse through products and add them to their cart, but am having trouble figuring out how to handle button presses within the tableViewCell. They way I want it to work is that when the empty circle button is pressed, the image changes to a filled circle with a checkmark, and the product within that cell is added to the customers "cart". The "cart" consists of two arrays products
and quantities
held within my CustomerOrder object.
Here’s what the tableView looks like:
Here’s my code so far:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ProductToBuyCell") as! ProductToBuyCell
//Configure the Selection Button
cell.selectButton.tag = indexPath.row
cell.selectButton.addTarget(self, action: #selector(ItemSelected), for: .touchUpInside)
let product = productsArray[indexPath.row]
//configure cell
cell.productImg.image = product.productPhoto
cell.productImg.contentMode = .scaleAspectFit
cell.productName.text = product.Name
cell.price.text = "$(product.price)"
return cell
}
// func for when the selectButton is tapped
// tag is equal to indexPath.row
@objc func ItemSelected(sender: UIButton) {
sender.imageView?.image = UIImage(systemName: "checkmark.circle.fill")
let product = productsArray[sender.tag]
newOrder.products.append(product)
newOrder.quantities.append(1)
}
So if someone could please explain how to properly handle events that are caused by elements within a UITableViewCell and how to get the buttons image to change, that would be very much appreciated.
2
Answers
UIButton
‘simageView
is read-only — to update its image you should usesetImage
. Note you’ll need to keep track of the selection state of the rows or else the selection state will be reset whenever the cell reloads (e.g. it scrolls out and back into view). Also presumably you’ll want to handle the case where the button is deselected.Also, since you’re only handling the selection on the
UIButton
in the cell, tapping the cell will not have any effect on the button’s state. You could instead implementtableView(_:didSelectRowAt:)
and handle the selection there, and just have a simpleUIImageView
instead of aUIButton
in your cell. You could also consider using theaccessoryView
.UITableView
also has built-in support for multiple selection, so if you turn that on it can keep track of the selected cells for you, and you just need to update the appearance of the cells accordingly.As for using the
tag
to figure out the cell index, it’s true it isn’t very robust but that wasn’t the question. Definitely worth checking out the linked question in the comments above for some good discussion around that.And yes, if you’re going to stick with this approach you should at least remove any existing actions before adding a new one to avoid duplicate events, e.g.
button.removeTarget(nil, action: nil, for: .allEvents)
.I assume your
Product
model is like this.To keep track of the selected product. You need to preserve the state of the cell. Create another model like this
As @ Paulw11 mentioned that "
tag
isn’t a great solution since the default is 0 and it will break if rows are reordered", you can use delegate pattern to get the selected product index.Create a protocol of the ProductToBuyCell.
Add a delegate property to cell.
Also add an
IBOutlet
andIBAction
of the button inProductToBuyCell
.Then add a selectionProduct property in the cell and set text of
UILabel
and image ofUIImage
using property observerdidSet
Modify the appearence of
UIImage
inawakeFromNib()
methodThen modify the
cellForRowAt
method like below. Here,productSelectionArray
is an array ofProductSection
notProduct
.Now confirm the
delegate
ofProductToBuyCell
toViewController
. Here, at first I change the state of the selected cell and then retrieve all the selected products usingfilter
andmap
function inproductSelectionArray
. And then reload the row to update UI.According to your UI it seems that all the selected products quantity is one, so you don’t have to maintain another array for quantity.