By ankit_rck


2013-12-18 10:06:00 8 Comments

I have a view controller with a table view and a separate nib for the table cell template. The cell template has some buttons. I want to access the button click along with the index of the cell clicked inside the view controller where I have defined the Table view.

So I have ViewController.h and ViewController.m where I have the UITableView and TableTemplate.h, TableTemplate.m and TableTemplate.xib where I have the nib defined. I want the button click event with cell index in ViewController.m.

Any help on how can I do that?

17 comments

@Radhe Yadav 2018-12-06 04:11:41

for swift 4:

inside the cellForItemAt ,
   
cell.chekbx.addTarget(self, action: #selector(methodname), for: .touchUpInside)

then outside of cellForItemAt
@objc func methodname()
{
//your function code
}

@valexa 2018-10-29 10:20:58

Use Swift closures :

class TheCell: UITableViewCell {

    var tapCallback: (() -> Void)?

    @IBAction func didTap(_ sender: Any) {
        tapCallback?()
    }
}

extension TheController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: TheCell.identifier, for: indexPath) as! TheCell {
            cell.tapCallback = {
                //do stuff
            }
            return cell
    }
}

@Yogesh Tarsariya 2018-07-03 12:08:08

Its Work For me.

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     UIButton *Btn_Play = (UIButton *)[cell viewWithTag:101];
     [Btn_Play addTarget:self action:@selector(ButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
}
-(void)ButtonClicked:(UIButton*)sender {
     CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.Tbl_Name];
     NSIndexPath *indexPath = [self.Tbl_Name indexPathForRowAtPoint:buttonPosition];
}

@Hitesh Chauhan 2018-05-18 11:24:20

// Add action in cell for row at index path -tableView

cell.buttonName.addTarget(self, action: #selector(ViewController.btnAction(_:)), for: .touchUpInside)

// Button Action

  @objc func btnAction(_ sender: AnyObject) {



        var position: CGPoint = sender.convert(.zero, to: self.tableView)


        let indexPath = self.tableView.indexPathForRow(at: position)
        let cell: UITableViewCell = tableView.cellForRow(at: indexPath!)! as
        UITableViewCell




}

@Mer 2017-11-09 11:40:41

CustomTableCell.h is a UITableViewCell:

@property (weak, nonatomic) IBOutlet UIButton *action1Button;
@property (weak, nonatomic) IBOutlet UIButton *action2Button;

MyVC.m after imports:

@interface MYTapGestureRecognizer : UITapGestureRecognizer
@property (nonatomic) NSInteger dataint;
@end

Inside "cellForRowAtIndexPath" in MyVC.m:

//CustomTableCell 
CustomTableCell *cell = (CustomTableCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];

//Set title buttons
[cell.action1Button setTitle:[NSString stringWithString:NSLocalizedString(@"action1", nil)] forState:UIControlStateNormal];
[cell.action2Button setTitle:[NSString stringWithString:NSLocalizedString(@"action2", nil)] forState:UIControlStateNormal];

//Set visibility buttons
[cell.action1Button setHidden:FALSE];
[cell.action2Button setHidden:FALSE];

//Do 1 action
[cell.action1Button addTarget:self action:@selector(do1Action :) forControlEvents:UIControlEventTouchUpInside];

//Do 2 action
MYTapGestureRecognizer *action2Tap = [[MYTapGestureRecognizer alloc] initWithTarget:self action:@selector(do2Action :)];
cancelTap.numberOfTapsRequired = 1;
cancelTap.dataint = indexPath.row;
[cell.action2Button setUserInteractionEnabled:YES];
[cell.action2Button addGestureRecognizer:action2Tap];

MyVC.m:

-(void)do1Action :(id)sender{
//do some action that is not necessary fr data
}

-(void)do2Action :(UITapGestureRecognizer *)tapRecognizer{
MYTapGestureRecognizer *tap = (MYTapGestureRecognizer *)tapRecognizer;
numberTag = tap.dataint;
FriendRequest *fr = [_list objectAtIndex:numberTag];

//connect with a WS o do some action with fr data

//actualize list in tableView
 [self.myTableView reloadData];
}

@Sevy11 2017-08-25 00:58:50

Swift 3 with a Closure

A nice solution is using a closure in a custom UITableViewCell to callback to the viewController for an action.

In cell:

final class YourCustomCell: UITableViewCell {

    var callbackClosure: (() -> Void)?

    // Configure the cell here
    func configure(object: Object, callbackClosure: (() -> Void)?) {
       self.callbackClosure = callbackClosure
    }


// MARK: - IBAction
extension YourCustomCell {
    @IBAction fileprivate func actionPressed(_ sender: Any) {
        guard let closure = callbackClosure else { return }
        closure()
    }
}

In View Controller: Tableview Delegate

extension YourViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        guard let cell: YourCustomCell = cell as? YourCustomCell else { return }
        cell.configure(object: object, callbackClosure: { [weak self] in
            self?.buttonAction()
        })
     }
 }

fileprivate extension YourViewController {

    func buttonAction() {
        // do your actions here 
    }
}

@yesleon 2017-04-18 14:16:58

I find it simplest to subclass the button inside your cell (Swift 3):

class MyCellInfoButton: UIButton {
    var indexPath: IndexPath?
}

In your cell class:

class MyCell: UICollectionViewCell {
    @IBOutlet weak var infoButton: MyCellInfoButton!
   ...
}

In the table view's or collection view's data source, when dequeueing the cell, give the button its index path:

cell.infoButton.indexPath = indexPath

So you can just put these code into your table view controller:

@IBAction func handleTapOnCellInfoButton(_ sender: MyCellInfoButton) {
        print(sender.indexPath!) // Do whatever you want with the index path!
}

And don't forget to set the button's class in your Interface Builder and link it to the handleTapOnCellInfoButton function!


edited:

Using dependency injection. To set up calling a closure:

class MyCell: UICollectionViewCell {
    var someFunction: (() -> Void)?
    ...
    @IBAction func didTapInfoButton() {
        someFunction?()
    }
}

and inject the closure in the willDisplay method of the collection view's delegate:

 func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    (cell as? MyCell)?.someFunction = {
        print(indexPath) // Do something with the indexPath.
    }
}

@Clifton Labrum 2017-11-01 05:05:04

The closure approach is the most Swifty way I've seen to do this. Nice work!

@user8132169 2017-06-08 14:57:45

cell.show.tag=indexPath.row;
     [cell.show addTarget:self action:@selector(showdata:) forControlEvents:UIControlEventTouchUpInside];

-(IBAction)showdata:(id)sender
{
    UIButton *button = (UIButton *)sender;

    UIStoryboard *storyBoard;
    storyBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    SecondViewController *detailView = [storyBoard instantiateViewControllerWithIdentifier:@"SecondViewController"];

    detailView.string=[NSString stringWithFormat:@"%@",[_array objectAtIndex:button.tag]];

    [self presentViewController:detailView animated:YES completion:nil];

}

@repoguy 2016-11-25 14:00:34

Instead of playing with tags, I took different approach. Made delegate for my subclass of UITableViewCell(OptionButtonsCell) and added an indexPath var. From my button in storyboard I connected @IBAction to the OptionButtonsCell and there I send delegate method with the right indexPath to anyone interested. In cell for index path I set current indexPath and it works :)

Let the code speak for itself:

Swift 3 Xcode 8

OptionButtonsTableViewCell.swift

import UIKit
protocol OptionButtonsDelegate{
    func closeFriendsTapped(at index:IndexPath)
}
class OptionButtonsTableViewCell: UITableViewCell {
    var delegate:OptionButtonsDelegate!
    @IBOutlet weak var closeFriendsBtn: UIButton!
    var indexPath:IndexPath!
    @IBAction func closeFriendsAction(_ sender: UIButton) {
        self.delegate?.closeFriendsTapped(at: indexPath)
    }
}

MyTableViewController.swift

class MyTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, OptionButtonsDelegate {...

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "optionCell") as! OptionButtonsTableViewCell
    cell.delegate = self
    cell.indexPath = indexPath
    return cell   
}

func closeFriendsTapped(at index: IndexPath) {
     print("button tapped at index:\(index)")
}

@Ulug'bek Ro'zimboyev 2017-01-05 06:10:02

can you help me, i am getting error at this line: class MyTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, OptionButtonsDelegate //error: Redundant conformance of 'MyTableViewController' to protocol 'UITableViewDataSource'

@repoguy 2017-01-05 11:29:05

looks like you are trying to conform to UITableViewDataSource multiple times. Perhaps you have got an extension where you already conform to data source?, can't help more without code

@Ulug'bek Ro'zimboyev 2017-01-05 12:35:06

@Milad Faridnia 2017-03-07 12:45:17

and how to pass data to perform segue and go to another view controller ?

@appsunited 2018-09-07 17:26:39

Best and cleanest solution!

@Himanshu padia 2016-07-07 13:38:57

Swift 2.2

You need to add target for that button.

myButton.addTarget(self, action: #selector(ClassName.FunctionName(_:), forControlEvents: .TouchUpInside)

FunctionName: connected // for example

And of course you need to set tag of that button since you are using it.

myButton.tag = indexPath.row

You can achieve this by subclassing UITableViewCell. Use it in interface builder, drop a button on that cell, connect it via outlet and there you go.

To get the tag in the connected function:

func connected(sender: UIButton) {
    let buttonTag = sender.tag
    // Do any additional setup
}

@Borzh 2015-10-13 13:40:52

@Mani answer is good, however tags of views inside cell's contentView often are used for other purposes. You can use cell's tag instead (or cell's contentView tag):

1) In your cellForRowAtIndexPath: method, assign cell's tag as index:

cell.tag = indexPath.row; // or cell.contentView.tag...

2) Add target and action for your button as below:

[cell.yourbutton addTarget:self action:@selector(yourButtonClicked:) forControlEvents:UIControlEventTouchUpInside];

3) Create method that returns row of the sender (thanks @Stenio Ferreira):

- (NSInteger)rowOfSender:(id)sender
{
    UIView *superView = sender.superview;
    while (superView) {
        if ([superView isKindOfClass:[UITableViewCell class]])
            break;
        else
            superView = superView.superview;
    }

    return superView.tag;
}

4) Code actions based on index:

-(void)yourButtonClicked:(UIButton*)sender
{
     NSInteger index = [self rowOfSender:sender];
     // Your code here
}

@Piyush Patel 2015-07-29 22:00:28

Following code might Help you.

I have taken UITableView with custom prototype cell class named UITableViewCell inside UIViewController.

So i have ViewController.h, ViewController.m and TableViewCell.h,TableViewCell.m

Here is the code for that:

ViewController.h

@interface ViewController : UIViewController<UITableViewDataSource,UITableViewDelegate>

@property (strong, nonatomic) IBOutlet UITableView *tblView;

@end

ViewController.m

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return (YourNumberOfRows);
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    static NSString *cellIdentifier = @"cell";

    __weak TableViewCell *cell = (TableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];

    if (indexPath.row==0) {
        [cell setDidTapButtonBlock:^(id sender)
         {
             // Your code here

         }];
    }    
    return cell;
}

Custom cell class :

TableViewCell.h

@interface TableViewCell : UITableViewCell

@property (copy, nonatomic) void (^didTapButtonBlock)(id sender);

@property (strong, nonatomic) IBOutlet UILabel *lblTitle;
@property (strong, nonatomic) IBOutlet UIButton *btnAction;

- (void)setDidTapButtonBlock:(void (^)(id sender))didTapButtonBlock;

@end

and

UITableViewCell.m

@implementation TableViewCell

- (void)awakeFromNib {
    // Initialization code
    [self.btnAction addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];

}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];

    // Configure the view for the selected state
}
- (void)didTapButton:(id)sender {
    if (self.didTapButtonBlock)
    {
        self.didTapButtonBlock(sender);
    }
}

Note: Here I have taken all UIControls using Storyboard.

Hope that can help you...!!!

@Daniel Raouf 2018-03-15 13:57:07

The best way ever

@Mani 2013-12-18 10:13:04

1) In your cellForRowAtIndexPath: method, assign button tag as index:

cell.yourbutton.tag = indexPath.row;

2) Add target and action for your button as below:

[cell.yourbutton addTarget:self action:@selector(yourButtonClicked:) forControlEvents:UIControlEventTouchUpInside];

3) Code actions based on index as below in ViewControler:

-(void)yourButtonClicked:(UIButton*)sender
{
     if (sender.tag == 0) 
     {
         // Your code here
     }
}

Updates for multiple Section:

You can check this link to detect button click in table view for multiple row and section.

@Sententia 2014-04-29 07:12:13

This can also be done through Interface Builder (IB) in step two. Just make sure your buttons tag is set. You really don't want to mix up your action calling. Either do it through IB or do it explicitly in your code.

@davecom 2014-05-31 04:08:00

@Mani It doesn't break MVC - the action is in the TableView not the Cell.

@Mani 2014-05-31 04:17:27

@davecom If you set button target as cell(via IB), How it will be trigger from tableView? Or Is their any way to connect button target to tableview which is placed in cell's xib?

@davecom 2014-05-31 06:41:55

@Mani Add a IBAction to the tableview and connect to it from the button in the cell.

@Mani 2014-05-31 06:58:29

I think, this is worst case. Connecting from cell's xib to tableview which is lie on other class.

@davecom 2014-06-02 05:32:09

Nope, pretty standard pattern actually.

@morroko 2014-08-04 06:55:54

Hi I have three button and 3 label (associated with each button) in each table view cell. Now I have to change text color of perticular label( associated with button) on button click how can I please suggest me

@Mani 2014-08-04 07:03:58

Take a look into this link about custom table view cell appcoda.com/customize-table-view-cells-for-uitableview. And now you could create custom table view cell which have three label with associative button that could place on label as invisible with custom button type. Write IBAction method for each button in table view cell and wired up with corresponding button which lie on label. I think, it will make sense and easiest way to do this.

@user2159978 2014-11-24 07:33:57

Is this approach correct? At least it seems incorrect to assign a tag value starting from zero.

@Mani 2014-11-24 10:32:17

@user2159978 In what case, it seems to be incorrect? could you please explain?

@user2159978 2014-11-24 10:37:45

does the system uses tags by itself to enumerate controls? Or even if no then what is the default tag value? Maybe zero which is used in this example?

@Mani 2014-11-24 10:55:27

@user2159978 did you see this line cell.yourbutton.tag = indexPath.row;. You've to initialize tag before use it. Otherwise it leads to unexpected result.

@user2159978 2014-11-24 11:23:07

the question was about does iOS system use tags which you haven't set by itself? and may these values correlate with tag = 0 you set in your own code?

@Vincent Cheong 2015-06-12 12:24:55

This solution runs into problems when you start inserting and deleting rows. The tag is not updated when rows are shifted. Instead of keeping a reference to the row. It may be better to keep a reference to a unique object id.

@Nischal Hada 2015-07-27 09:20:50

check this question dude stackoverflow.com/questions/31649220/…

@Mani 2015-07-27 10:01:41

@nischalhada This trick shouldn't work for your question (Multiple sections). You should go with another approach.

@TigerCoding 2015-12-29 19:44:18

Anytime you find yourself assigning values to the tag attributes of views, you have a very bad code smell that can bite you later on. Look for better ways to achieve your goal, not the first S.O. post you find.

@Mani 2015-12-30 07:02:36

@TigerCoding I agree with the point "Look for better ways to achieve" But It's not too bad code, if so, S.O user won't give votes like this and also that cann't bit you later on. If you doing wrongly, its bite you right now. I know, This code won't help you for section wise datasource. See older comments.

@GoodSp33d 2014-07-13 08:06:28

Delegates are the way to go.

As seen with other answers using views might get outdated. Who knows tomorrow there might be another wrapper and may need to use cell superview]superview]superview]superview]. And if you use tags you would end up with n number of if else conditions to identify the cell. To avoid all of that set up delegates. (By doing so you will be creating a re usable cell class. You can use the same cell class as a base class and all you have to do is implement the delegate methods.)

First we need a interface (protocol) which will be used by cell to communicate(delegate) button clicks. (You can create a separate .h file for protocol and include in both table view controller and custom cell classes OR just add it in custom cell class which will anyway get included in table view controller)

@protocol CellDelegate <NSObject>
- (void)didClickOnCellAtIndex:(NSInteger)cellIndex withData:(id)data;
@end

Include this protocol in custom cell and table view controller. And make sure table view controller confirms to this protocol.

In custom cell create two properties :

@property (weak, nonatomic) id<CellDelegate>delegate;
@property (assign, nonatomic) NSInteger cellIndex;

In UIButton IBAction delegate click : (Same can be done for any action in custom cell class which needs to be delegated back to view controller)

- (IBAction)buttonClicked:(UIButton *)sender {
    if (self.delegate && [self.delegate respondsToSelector:@selector(didClickOnCellAtIndex:withData:)]) {
        [self.delegate didClickOnCellAtIndex:_cellIndex withData:@"any other cell data/property"];
    }
}

In table view controller cellForRowAtIndexPath after dequeing the cell, set the above properties.

cell.delegate = self;
cell.cellIndex = indexPath.row; // Set indexpath if its a grouped table.

And implement the delegate in table view controller:

- (void)didClickOnCellAtIndex:(NSInteger)cellIndex withData:(id)data
{
    // Do additional actions as required.
    NSLog(@"Cell at Index: %d clicked.\n Data received : %@", cellIndex, data);
}

This would be the ideal approach to get custom cell button actions in table view controller.

@JulianSymes 2014-07-17 11:17:19

Why have you made the delegate a strong property of the cell? This will give you a retain cycle, unless you know the controller only weakly holds the cell.

@skornos 2015-03-07 09:17:09

what about the _cellIndex beign updated after the cell is deleted?

@Bista 2015-07-10 04:38:29

I heard from one of my friend saying using delegate on each cell causes memory consumption, so use tags. Is this true?

@Nischal Hada 2015-07-27 09:21:04

check this question dude stackoverflow.com/questions/31649220/…

@Ian Warburton 2015-11-30 15:41:25

@the_UB There can't be much between setting a tag and storing a single reference. Possibly a tag would take up more memory.

@Priest 2016-12-20 05:22:15

Clean solution @GoodSp33d , however your UITableViewCell shouldn't be returning any (id)data in it's protocol, since this would violate the MVC principle, where the View is also handling the Model. Not to mention that cells are meant to be reusable, and thus should be as decoupled as possible from their parent views. Once the cell returns it's cellIndex, the UIViewController conforming to it's protocol (and acting as the UITableView's parent), should handle fetching the proper data for the cell, from the UITableView's datasource.

@Learn OpenGL ES 2017-06-05 23:44:19

Storing the index path isn't necessary, because you can just call tableView.indexPath(for: cell)

@Yogesh Lolusare 2014-07-30 10:54:26

The reason i like below technique because it also help me to identify the section of table.

Add Button in cell cellForRowAtIndexPath:

 UIButton *selectTaskBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        [selectTaskBtn setFrame:CGRectMake(15, 5, 30, 30.0)];
        [selectTaskBtn setTag:indexPath.section]; //Not required but may find useful if you need only section or row (indexpath.row) as suggested by MR.Tarun 
    [selectTaskBtn addTarget:self action:@selector(addTask:)   forControlEvents:UIControlEventTouchDown];
[cell addsubview: selectTaskBtn];

Event addTask:

-(void)addTask:(UIButton*)btn
{
    CGPoint buttonPosition = [btn convertPoint:CGPointZero toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    if (indexPath != nil)
    {
     int currentIndex = indexPath.row;
     int tableSection = indexPath.section;
    }
}

Hopes this help.

@Nischal Hada 2015-07-27 09:21:24

check this question dude stackoverflow.com/questions/31649220/…

@Stenio Ferreira 2014-03-27 16:29:30

Tarun's code doesnt work on iOS7, since the UITableViewCell structure changed and now he would get "UITableViewCellScrollView" instead.

This post Getting UITableViewCell with superview in iOS 7 has a good solution creating a loop to find the correct parent view, regardless of any future changes in the structure. It boils down to creating a loop:

    UIView *superView = [sender superview];
    UIView *foundSuperView = nil;

    while (nil != superView && nil == foundSuperView) {
        if ([superView isKindOfClass:[UITableViewCell class]]) {
            foundSuperView = superView;
        } else {
            superView = superView.superview;
        }
    }

The link has code for a more reusable solution, but this should work.

@Tarun 2013-12-18 10:13:29

This should help :-

UITableViewCell* cell = (UITableViewCell*)[sender superview];
NSIndexPath* indexPath = [myTableView indexPathForCell:cell];

Here sender is the UIButton instance that is sending the event. myTableView is the UITableView instance you're dealing with.

Just get the cell reference right and all the work is done.

You may need to remove the buttons from cell's contentView & add them directly to UITableViewCell instance as it's subview.

Or

You can formulate a tag naming scheme for different UIButtons in cell.contentView. Using this tag, later you can know the row & section information as needed.

@pierre23 2014-04-23 00:10:46

it should be [[sender superview] superview];

@Sententia 2014-04-29 07:09:24

This is good for very simple cells. However, if your cell has a deep tree of views Mani's answer is best.

@Rajan Maharjan 2014-05-22 07:47:47

Now in iOS 7 it should be UITableViewCell* cell = (UITableViewCell *)[[[sender superview] superview] superview]; Thank you.

@Babul 2014-11-06 06:14:38

Please use @HaloZero code, It is perfect and works in iOS 8

@Nischal Hada 2015-07-27 09:21:09

check this question dude stackoverflow.com/questions/31649220/…

Related Questions

Sponsored Content

12 Answered Questions

[SOLVED] Having a UITextField in a UITableViewCell

26 Answered Questions

[SOLVED] Remove empty space before cells in UITableView

17 Answered Questions

[SOLVED] UITableViewCell, show delete button on swipe

11 Answered Questions

[SOLVED] How to hide 'Back' button on navigation bar on iPhone?

8 Answered Questions

[SOLVED] Is it possible to refresh a single UITableViewCell in a UITableView?

6 Answered Questions

1 Answered Questions

[SOLVED] Can't click button inside UITableViewCell?

1 Answered Questions

4 Answered Questions

click button action in sequentially order in UITableView

1 Answered Questions

Differentiate button and row click event in Table View

Sponsored Content