Detecting which UIButton was pressed in a UITableView

前端 未结 26 2905
小蘑菇
小蘑菇 2020-11-22 00:40

I have a UITableView with 5 UITableViewCells. Each cell contains a UIButton which is set up as follows:

- (UITableView         


        
26条回答
  •  无人共我
    2020-11-22 00:50

    With Swift 4.2 and iOS 12, you can choose one the 5 following complete examples in order to solve your problem.


    #1. Using UIView's convert(_:to:) and UITableView's indexPathForRow(at:)

    import UIKit
    
    private class CustomCell: UITableViewCell {
    
        let button = UIButton(type: .system)
    
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
    
            button.setTitle("Tap", for: .normal)
            contentView.addSubview(button)
    
            button.translatesAutoresizingMaskIntoConstraints = false
            button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
            button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
            button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
            button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
    }
    
    import UIKit
    
    class TableViewController: UITableViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
        }
    
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 3
        }
    
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
            cell.button.addTarget(self, action: #selector(customCellButtonTapped), for: .touchUpInside)
            return cell
        }
    
        @objc func customCellButtonTapped(_ sender: UIButton) {
            let point = sender.convert(CGPoint.zero, to: tableView)
            guard let indexPath = tableView.indexPathForRow(at: point) else { return }
            print(indexPath)
        }
    
    }
    

    #2. Using UIView's convert(_:to:) and UITableView's indexPathForRow(at:) (alternative)

    This is an alternative to the previous example where we pass nil to the target parameter in addTarget(_:action:for:). This way, if the first responder does not implement the action, it will be send to the next responder in the responder chain until until a proper implementation is found.

    import UIKit
    
    private class CustomCell: UITableViewCell {
    
        let button = UIButton(type: .system)
    
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
    
            button.setTitle("Tap", for: .normal)
            button.addTarget(nil, action: #selector(TableViewController.customCellButtonTapped), for: .touchUpInside)
            contentView.addSubview(button)
    
            button.translatesAutoresizingMaskIntoConstraints = false
            button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
            button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
            button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
            button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
    }
    
    import UIKit
    
    class TableViewController: UITableViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
        }
    
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 3
        }
    
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
            return cell
        }
    
        @objc func customCellButtonTapped(_ sender: UIButton) {
            let point = sender.convert(CGPoint.zero, to: tableView)
            guard let indexPath = tableView.indexPathForRow(at: point) else { return }
            print(indexPath)
        }
    
    }
    

    #3. Using UITableView's indexPath(for:) and delegate pattern

    In this example, we set the view controller as the delegate of the cell. When the cell's button is tapped, it triggers a call to the appropriate method of the delegate.

    import UIKit
    
    protocol CustomCellDelegate: AnyObject {
        func customCellButtonTapped(_ customCell: CustomCell)
    }
    
    class CustomCell: UITableViewCell {
    
        let button = UIButton(type: .system)
        weak var delegate: CustomCellDelegate?
    
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
    
            button.setTitle("Tap", for: .normal)
            button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
            contentView.addSubview(button)
    
            button.translatesAutoresizingMaskIntoConstraints = false
            button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
            button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
            button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
            button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        @objc func buttonTapped(sender: UIButton) {
            delegate?.customCellButtonTapped(self)
        }
    
    }
    
    import UIKit
    
    class TableViewController: UITableViewController, CustomCellDelegate {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
        }
    
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 3
        }
    
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
            cell.delegate = self
            return cell
        }
    
        // MARK: - CustomCellDelegate
    
        func customCellButtonTapped(_ customCell: CustomCell) {
            guard let indexPath = tableView.indexPath(for: customCell) else { return }
            print(indexPath)
        }
    
    }
    

    #4. Using UITableView's indexPath(for:) and a closure for delegation

    This is an alternative to the previous example where we use a closure instead of a protocol-delegate declaration to handle the button tap.

    import UIKit
    
    class CustomCell: UITableViewCell {
    
        let button = UIButton(type: .system)
        var buttontappedClosure: ((CustomCell) -> Void)?
    
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
    
            button.setTitle("Tap", for: .normal)
            button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
            contentView.addSubview(button)
    
            button.translatesAutoresizingMaskIntoConstraints = false
            button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
            button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
            button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
            button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        @objc func buttonTapped(sender: UIButton) {
            buttontappedClosure?(self)
        }
    
    }
    
    import UIKit
    
    class TableViewController: UITableViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
        }
    
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 3
        }
    
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
            cell.buttontappedClosure = { [weak tableView] cell in
                guard let indexPath = tableView?.indexPath(for: cell) else { return }
                print(indexPath)
            }
            return cell
        }
    
    }
    

    #5. Using UITableViewCell's accessoryType and UITableViewDelegate's tableView(_:accessoryButtonTappedForRowWith:)

    If your button is a UITableViewCell's standard accessory control, any tap on it will trigger a call to UITableViewDelegate's tableView(_:accessoryButtonTappedForRowWith:), allowing you to get the related index path.

    import UIKit
    
    private class CustomCell: UITableViewCell {
    
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            accessoryType = .detailButton
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
    }
    
    import UIKit
    
    class TableViewController: UITableViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
        }
    
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 3
        }
    
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
            return cell
        }
    
        override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
            print(indexPath)
        }
    
    }
    

提交回复
热议问题