Detecting which UIButton was pressed in a UITableView

前端 未结 26 2903
小蘑菇
小蘑菇 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:44

    How about sending the information like NSIndexPath in the UIButton using runtime injection.

    1) You need runtime on the import

    2) add static constant

    3) add NSIndexPath to your button on runtime using:

    (void)setMetaData:(id)target withObject:(id)newObj

    4) on button press get metadata using:

    (id)metaData:(id)target

    Enjoy

        #import <objc/runtime.h>
        static char const * const kMetaDic = "kMetaDic";
    
    
        #pragma mark - Getters / Setters
    
    - (id)metaData:(id)target {
        return objc_getAssociatedObject(target, kMetaDic);
    }
    
    - (void)setMetaData:(id)target withObject:(id)newObj {
        objc_setAssociatedObject(target, kMetaDic, newObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    
    
        #On the cell constructor
        - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
        {
        ....
        cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        ....
        [btnSocial addTarget:self
                                       action:@selector(openComments:)
                             forControlEvents:UIControlEventTouchUpInside];
    
        #add the indexpath here or another object
        [self setMetaData:btnSocial withObject:indexPath];
    
        ....
        }
    
    
    
        #The action after button been press:
    
        - (IBAction)openComments:(UIButton*)sender{
    
            NSIndexPath *indexPath = [self metaData:sender];
            NSLog(@"indexPath: %d", indexPath.row);
    
            //Reuse your indexpath Now
        }
    
    0 讨论(0)
  • 2020-11-22 00:45

    Am I missing something? Can't you just use sender to identify the button. Sender will give you info like this:

    <UIButton: 0x4b95c10; frame = (246 26; 30 30); opaque = NO; tag = 104; layer = <CALayer: 0x4b95be0>>
    

    Then if you want to change the properties of the button, say the background image you just tell sender:

    [sender setBackgroundImage:[UIImage imageNamed:@"new-image.png"] forState:UIControlStateNormal];
    

    If you need the tag then ACBurk's method is fine.

    0 讨论(0)
  • 2020-11-22 00:47

    Here's how I do it. Simple and concise:

    - (IBAction)buttonTappedAction:(id)sender
    {
        CGPoint buttonPosition = [sender convertPoint:CGPointZero
                                               toView:self.tableView];
        NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
        ...
    }
    
    0 讨论(0)
  • 2020-11-22 00:47

    Subclass the button to store the required value, maybe create a protocol (ControlWithData or something). Set the value when you add the button to the table view cell. In your touch up event, see if the sender obeys the protocol and extract the data. I normally store a reference to the actual object that is rendered on the table view cell.

    0 讨论(0)
  • 2020-11-22 00:49

    I always use tags.

    You need to subclass the UITableviewCell and handle the button press from there.

    0 讨论(0)
  • 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)
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题