-4

How do I handle a custom UIView button action inside a TableViewCell?

I have custom UIView with XIB which I added to TableView which is implementet in UIViewControler. For each cell I add my custom UIView in tableView function - cellForRowAt. Everything looks fine but I can't handle button action from added custom UIView for that cell. Can someone help me how to do that?

Edit:

My custom UIView which has own XIB.

protocol TicketButtonDelegate {
    func starButtonAction(_: UIButton)
}

class TicketView: UIView {

    @IBOutlet var ticketContentView : UIView!
    var delegate: TicketButtonDelegate!

    @IBOutlet weak var starButton : UIButton!

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    private func commonInit() {

        Bundle.main.loadNibNamed("TicketView", owner: self, options: nil)
        addSubview(ticketContentView)

        starButton.addTarget(self, action: #selector(starButtonAction(_:)), for: .touchUpInside)

        ticketContentView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 132)
    }

    @objc func starButtonAction(_ sender: UIButton) {
        delegate.starButtonAction(sender)
    }
}

My UIViewController.

class MhdDashboardBottom: UIViewController, TicketButtonDelegate {
    @IBOutlet weak var mhdTicketsTable: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        mhdTicketsTable.delegate = self
        mhdTicketsTable.dataSource = self
        mhdTicketsTable.register(UINib(nibName: "MhdTicketTableCell", bundle: nil), forCellReuseIdentifier: "MhdTicketCell")
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellIdentifier = "MhdTicketCell"

        guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? MhdTicketTableCell else {
            fatalError("The dequeued cell is not an instance of MhdTicketTableCell")
        }

        let ticket = tickets[indexPath.row]
        let ticketCell = TicketView()
        ticketCell.delegate = self
        ticketCell.tag = 700
        var viewExists = false
        for view in cell.contentCellView.subviews {
            if view.tag == ticketCell.tag {
                viewExists = true
                break
            }
        }
        if viewExists == false {
            cell.contentCellView.addSubview(ticketCell)
        }
        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 132
    }

    func starButtonAction(_: UIButton) {
        print("Works?")
    }
}

My MhdTicketTableCell (UITableViewCell)

class MhdTicketTableCell: UITableViewCell {
    @IBOutlet weak var contentCellView: UIView!
}
MarekB
  • 612
  • 6
  • 12

2 Answers2

2

Rather than a protocol use a callback closure, it avoids view hierarchy math, tags and protocol declaration:

  • In the view delete the protocol

    protocol TicketButtonDelegate {
       func starButtonAction(_: UIButton)
    } 
    

  • Replace var delegate: TicketButtonDelegate! with weak var callback: (() -> Void)?

  • Replace

    @objc func starButtonAction(_ sender: UIButton) {
        delegate.starButtonAction(sender)
    }
    

    with

    @objc func starButtonAction(_ sender: UIButton) {
        callback?()
    }
    
  • In the controller delete

    func starButtonAction(_: UIButton) {
        print("Works?")
    }
    

  • Replace

    ticketCell.delegate = self
    

    with

    ticketCell.callback = {
       print("Works?", indexPath)
    }
    

The index path and even the ticket are captured.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • I tried exactly how you wrote but result is same. I edited my code. Look at method in UIViewController cellForRow where Im adding view to cell. Can be somewhere there problem? – MarekB Jan 10 '19 at 12:32
  • Yes, I'm quite sure that assigning the view causes the issue. Why don't you design and hook up everything in Interface Builder? – vadian Jan 10 '19 at 12:40
  • Because I want view of cell to be reusable as View in other parts of app. – MarekB Jan 10 '19 at 12:40
0

Well after very long research for answer I come up with solution.

Into my TicketView I had to add this function to pass touch events into subviews.

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for subview in subviews {
        if !subview.isHidden && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) {
            return true
        }
    }
    return false
}

After this, I deleted delegate implementation and I just added

ticketCell.starButton.addTarget(self, action: #selector(starButtonAction(_:)), for: .touchUpInside)

into UIViewControlers cellForRow function and then added objc function

@objc func starButtonAction(_ sender: UIButton) {
    print("Works?")
}

All thanks to this answered question.

MarekB
  • 612
  • 6
  • 12
  • Weird. I also have buttons inside cells and can easily assign actions there without hit testing... – LinusGeffarth Jan 10 '19 at 14:10
  • I have buttons inside custom UIView which has assigned XIB View which is added as subview to TabvleViewCell container. So touch events weren't handled to my custom UIView. – MarekB Jan 10 '19 at 15:33