0

I have a custom tableviewHeaderFooterView where I set up a target event for the button in the custom tableViewCell class (checkButton is the button and its background image changes to a checkmark when a user clicks on it).

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

    let userModel = Data.userModels[section]
    let cell = tableView.dequeueReusableCell(withIdentifier: "cellId") as! SectionHeader

    cell.setup(model: userModel)
    cell.checkButton.tag = section
    cell.checkButton.addTarget(self, action: #selector(handleTap), for: .touchUpInside) 

    return cell.contentView
}

And in that function I want to create or remove items from an array depending on whether the user taps on a cell or not (i.e. if they tap on the button, then add something to the array, but if they tap on that button again, then remove that object from the array.)

@objc func handleTap(sender: UIButton) {
    sender.isSelected = !sender.isSelected

    if sender.isSelected == true {
        let model = ItemModel(itemName: item, price: price)
        ItemModelFunctions.createItem(for: sender.tag, using: model)
    }
    if sender.isSelected == false {
        ItemModelFunctions.removeFromUser(from: sender.tag)
    }
    print(sender.tag)
}

Here are the createItem and removeFromUser functions:

  struct ItemModelFunctions {
    static func createItem(for userIndex: Int, using itemModel: ItemModel) {
        Data.userModels[userIndex].itemModels.append(itemModel)

    }
    static func removeFromUser(from userIndex: Int) {
        Data.itemModels.remove(at: userIndex)
    }
}

When I tap on the button twice to remove it from the array, I get an error saying Data.itemModels.remove(at: userIndex) is out of range.

I know using a prototype cell for a tableViewHeaderFooterView isn't exactly the correct way, but I've seen other programmers and YouTubers do this with success. Are my issues coming from using a prototype cell? Or am I removing the item from the array in the wrong way? Thank you all for your help!

3 Answers 3

2
   // Please maintain one more array i.e selectedIndexArray and follow below code.

     var  selectedIndexArray  = [Integer]()

        @IBAction func buttonTapped(_ sender: UIButton) {

            let button = sender
            if selectedIndexArray.contains(button.tag) {
                button.tag --> Remove this tag from  selectedIndexArray
                let model = ItemModel(itemName: item, price: price)
                ItemModelFunctions.createItem(for: sender.tag, using: model)
            } else
            {
                selectedIndexArray.append(button.tag)
                ItemModelFunctions.removeFromUser(from: sender.tag)
            }
            //reload tableview.
self.tableView.reloadData()
        }
Sign up to request clarification or add additional context in comments.

Comments

1

The checkButton.addTarget function will run each time your section header is reused. Then it will duplicate event for cell when reuse many times. I think you should not use this solution. Instead of that, I think you should write delegate way to solve your problem. Ex:

protocol SectionHeaderDelegate: class {
    func checkButtonDidTap(section: SectionHeader)
}

class SectionHeader: UITableViewCell {
    weak var delegate: SectionHeaderDelegate
    @IBOutlet weak var checkButton: UIButton!

    override func awakeFromNib() {
        super.awakeFromNib()

        checkButton.addTarget(self, action: #selector(handleTap), for: .touchUpInside) 
    }

    func handleTap() {
        self.delegate.checkButtonDidTap(section: self)
    }
}

and you set cell.delegate = self in viewForHeaderInSection function. And implement protocol SectionHeaderDelegate.

6 Comments

This is very helpful, but could you help me understand? So in the protocol method is where I would write the code that I originally had in the handleTap function?
Yes, SectionHeader call delegate when checkButton tapped to pass event to your viewController. You need implement method in SectionHeaderDelegate to handle. In that method you will code like @objc func handleTap(sender: UIButton) function. :D. section variable help you find indexPath of cell did tap.
You can make reference here: fluffy.es/…
I tried out this solution and tried printing a string just to see if it's being called and it doesn't seem that the protocol function in my ViewController is being called.
Did you forget call function through delegate variable? or forget set delegate property
|
1

Thanh Vu's solution works. Another solution would be to add an IBAction to your ViewController:

@IBAction func buttonTapped(_ sender: UIButton) {
    sender.isSelected = !sender.isSelected
    let cell = sender.superview?.superview as! SectionHeader
    if let indexPath = mainTableView.indexPath(for: cell) {
        // if sender.isSelected etc...
    }
}

2 Comments

would this approach work even if the button was reused in all of my cells? Like would I be able to change properties of the buttons for certain cells? Sorry I'm new to swift and am trying to understand.
Yes this would work even if the button was reused in all of your cells. In order to change properties for individual buttons, just do that inside your cellForRowAt function. And if you want to know which button is the sender, use the tag property.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.