Is it wrong to add action to button in tableViewCell with tag?

I have a UItableViewCell with a button inside it, I set the tag of the button and add the action of the button in my ViewController using the tag.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "BillHistoryTableViewCell", for: indexPath) as! BillHistoryTableViewCell
        let cellData = billHistories[indexPath.row]
        cell.setup(with: cellData)
        cell.retryButton.tag = indexPath.row
        return cell
}
@IBAction func billHistoryRetryButtonDidTap(_ sender: UIButton) {
        let index = sender.tag
        if let id = billHistories[index].transactionInfo?.billUniqueID {
            hidePayIdGeneralTextField()
            billIdTextField.text = id.toNormalNumber()
            inquiryGeneralBillRequest()
        }
}

I want to know is it wrong for any reason? someone told me it is not good because it uses lots of memory to use tags.

1 answer

  • answered 2022-01-19 21:41 Maor Atlas

    Will it work? yes, but as mentioned above, this is not the best approach, I'd avoid using tags unless this is just for some POC. There are better approaches to handle it. The first I'd suggest is using delegation to inform back to the controller, here's an example:

    class BillHistoryTableViewController {
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "BillHistoryTableViewCell", for: indexPath) as! BillHistoryTableViewCell
            let cellData = billHistories[indexPath.row]
            cell.setup(with: cellData)
            cell.index = indexPath.row
            
            cell.delegate = self
            return cell
        }
    }
    
    extension BillHistoryTableViewController: BillHistoryTableViewCellDelegate {
        func didTapButton(index: Int) {
            print("tapped cell with index:\(index)")
    
            if let id = billHistories[index].transactionInfo?.billUniqueID {
                hidePayIdGeneralTextField()
                billIdTextField.text = id.toNormalNumber()
                inquiryGeneralBillRequest()
            }
        }
    }
    
    protocol BillHistoryTableViewCellDelegate: AnyObject {
        func didTapButton(index: Int)
    }
    
    class BillHistoryTableViewCell: UITableViewCell {
        weak var delegate: BillHistoryTableViewCellDelegate?
        var cellData: CellData?
        var index: Int?
        
        func setup(with cellData: CellData) {
            self.cellData = cellData
        }
        
        @IBAction func buttonPressed(_ sender: UIButton) {
            guard let index = index else {
                return
            }
            
            delegate?.didTapButton(index: index)
        }
    }
    

    Another approach that I prefer lately is using Combine's PassThroughSubject, it requires less wiring and delegate definitions.

    import Combine
    
    class BillHistoryTableViewController {
        var cancellable: AnyCancellable?
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "BillHistoryTableViewCell", for: indexPath) as! BillHistoryTableViewCell
            let cellData = billHistories[indexPath.row]
            cell.setup(with: cellData)
            cell.index = indexPath.row
            
            cancellable = cell.tappedButtonSubject.sink { [weak self] index in
                guard let self = self else { return }
                print("tapped cell with index:\(index)")
    
                if let id = self.billHistories[index].transactionInfo?.billUniqueID {
                    self.hidePayIdGeneralTextField()
                    self.billIdTextField.text = id.toNormalNumber()
                    self.inquiryGeneralBillRequest()
                }
            }
            
            return cell
        }
    }
    
    class BillHistoryTableViewCell: UITableViewCell {
        var tappedButtonSubject = PassthroughSubject<Int, Never>()
        
        var cellData: CellData?
        var index: Int?
        
        func setup(with cellData: CellData) {
            self.cellData = cellData
        }
        
        @IBAction func buttonPressed(_ sender: UIButton) {
            guard let index = index else {
                return
            }
            
            tappedButtonSubject.send(index)
        }
    }
    

    You can make it even shorter by injecting the index with the cellData, e.g:

    func setup(with cellData: CellData, index: Int) {
        self.cellData = cellData
        self.index = index
    }
    

    but from what I see in your example, you don't even need the index, you just need the CellData, so if we'll take the Combine examples these are the main small changes you'll have to make:

    var tappedButtonSubject = PassthroughSubject<CellData, Never>()
    
    tappedButtonSubject.send(cellData)
    

    and observing it by:

    cancellable = cell.tappedButtonSubject.sink { [weak self] cellData in
        if let id = cellData.transactionInfo?.billUniqueID {
            //
        }
    }
    

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum