user3576169
user3576169

Reputation: 81

If a UITableViewCell has multiple buttons, where is the best place to handle touch events

I'm presenting a lot of data in format of a table with multiple columns. Almost each column has a button (up to 4 in total) and each row is a UITableViewCell.

How could I detect that the buttons were touched and where should I handle touch events of the buttons? I'm certain, it shouldn't be a didSelectRowAtIndexPath method though.

As soon as, I could detect which button was pressed, I would fetch the data in that particular row and manipulate it. So, I need to know the indexPath of the row, as well as what button was pressed on it.

Upvotes: 1

Views: 2279

Answers (7)

A'sa Dickens
A'sa Dickens

Reputation: 2085

This is a personal preference on how I like to handle situations like these, but I would first subclass UITableViewCell because your table cells do not look like a default iOS UITableViewCell. Basically you have a custom set up, so you need a custom class.

From there you should set up your 4 IBActions in your header file

- (IBAction)touchFirstButton;
- (IBAction)touchSecondButton;
- (IBAction)touchThirdButton;
- (IBAction)touchFourthButton;

You do not need to pass a sender in these actions, because you will not be using that object in these methods. They are being created to forward the call.

After that set up a protocol for your UITableViewSubClass

@protocol UITableViewSubClassDelegate;

Remember to put that outside and before the @interface declaration

Give your sell a delegate property

@property (nonatomic, strong) id<UITableViewSubClassDelegate> delegate;

and finally define your actual protocol, you will need to set up 4 methods, 1 for each button and take your subclass as a parameter

@protocol UITableViewSubClassDelegate <NSObject>

   - (void)forwardedFirstButtonWithCell:(UITableViewSubClass*)cell;
   - (void)forwardedSecondButtonWithCell:(UITableViewSubClass*)cell;
   - (void)forwardedThirdButtonWithCell:(UITableViewSubClass*)cell;
   - (void)forwardedFourthButtonWithCell:(UITableViewSubClass*)cell;

@end

This will be placed outside of the @interface @end section at the bottom

After that create a configureWithModel: method in your @interface and @implementation as well as a property for your model

@interface:

@property (nonatomic, strong) Model *model;
- (void)configureWithModal:(Model*)model;

@implementation:

- (void)configureWithModal:(Model*)model {
   self.model = model;
   // custom UI set up
}

From here you should configure your action methods in your @implementation file to call the delegate methods, i'm only showing the first one, but you would do this with all of the IBActions

- (void)configureWithModal:(Model*)model {
   [self.delegate forwardFirstButtonWithCell:self];
}

From here your custom cell set up is done and we need to go back to the UIViewController that is displaying the UITableView. First go into the header file of the view controller, and import your custom UITableViewCellSubClass and then setup the class to implement this protocol.

It should look something like this

@interface MYViewController : UIViewController <UITableViewSubClassDelegate>

from there you should go into your cellForRowAtIndexPath: method and configure your custom UITableViewCell

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

   UITableViewCellSubClass *cell = [tableView dequeueReusableCellWithIdentifier:@"CellIdentifier"];
   cell.delegate = self;

   Model *cellModel = self.tableData[indexPath.row];
   [cell configureWithModel:cellModel];

   return cell;

}

Now go into your cell class and copy paste all of the protocol methods into your viewController class. I will display one as an example.

In your UIViewController:

- (void)forwardedFirstButtonWithCell:(UITableViewSubClass*)cell {
   Model *cellModel = cell.model;
   // do stuff with model from outside of the cell
}

do that for all methods and you should be good.

Remember to have all your #imports in so there's no forward declarations and remember to link up the IBActions to your storyboard or xib files. If you want a custom xib for your table cell you will have to check if the cell is nil and then allocate a new one, but if you are using prototype cells then this should be sufficient.

For simplicity sakes i put forwardFirstButtonWithCell: but i would encourage making the name something that describes what it's doing such as, displayPopOverToEnterData or something similar. From there you could even change the parameters of the delegate protocol methods to take models instead so instead of

- (void) displayPopOverToEnterDataWithCell:(UITableViewSubClass*)cell;

make it

- (void) displayPopOverToEnterDataWithModel:(Model*)model;

but, i don't know what type of information you need to access from the cell. So update these methods as you see fit.

Upvotes: 0

Isaac Paul
Isaac Paul

Reputation: 1977

Generalized undetailed answer:

  1. Create UITableviewcell subclass, link cell ui elements to this class.
  2. Add method configureWithModel:(Model*)model; //Model being the information you want the cell to represent
  3. Manipulate that information or
  4. If you need to manipulate the screen or other objects. You need to give the table view cell subclass a reference to the other objects when the cell is created. (in code or in storyboard or in nib).

how to handle button presses in ios 7: Button in UITableViewCell not responding under ios 7 (set table cell selection to none)

how to link a button: http://oleb.net/blog/2011/06/creating-outlets-and-actions-via-drag-and-drop-in-xcode-4/

Upvotes: 2

mithlesh jha
mithlesh jha

Reputation: 343

Ideally, you should create a custom cell by subclassing UITableViewCell and implement the actions for each of these buttons in that cell. If your view controller needs to know about these actions, you can define a protocol, MyCustomCellDelegate or similar, and have your view controller conformed to that protocol. Then MyCustomCell will be able to send messages to the view controller when user interacts with its buttons or other controls.

As in the example code below, you can create a cell in storyboard or nib and hook one of the button's action to firstButtonAction method of CustomTableCell class.

Also, you need to set your view controller as delegate property of CustomTableCell object created and implement the method buttonActionAtIndex: of CustomTableCellDelegate in your view controller class. Use controlIndexInCell param passed to this method to determine which button might have generated the action.

   @protocol CustomTableCellDelegate <NSObject>

   - (void) buttonActionAtIndex:(NSInteger)controlIndexInCell

   @end

In CustomTableCell.h class

   @interface CustomTableCell: UITableViewCell
   @property (nonatomic, weak) id <CustomTableCellDelegate> delegate

   - (IBAction) firstButtonAction:(id)sender

   @end

In CustomTableCell.m class

   @implementation CustomTableCell
   @synthesize delegate

   - (IBAction) firstButtonAction:(id)sender{
         if ([delegate respondToSelector:@selector(buttonActionAtIndex:)])
            [delegate buttonActionAtIndex:0];
    }

   @end

Upvotes: 0

Jeffery Thomas
Jeffery Thomas

Reputation: 42598

Because each button has a different action, the only thing you need to get at runtime is the indexPath of the button. That can be done by looking at the button's superviews until a cell is found.

- (IBAction)action1:(UIButton *)button
{
    UITableViewCell *cell = [self cellContainingView:button];
    NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];

    MyDataModel *object = self.objects[indexPath.row];
    // perform action1 on the data model object

    // Now that the data model behind indexPath.row was done, reload the cell
    [self.tableView reloadRowsAtIndexPaths:@[indexPath]
                          withRowAnimation: UITableViewRowAnimationAutomatic];
}

- (id)cellContainingView:(UIView *)view
{
    if (view == nil)
        return nil;

    if ([view isKindOfClass:[UITableViewCell class]])
        return view;

    return [self cellContainingView:view.superview];
}

There: no delegates, no tags, and the action doesn't care about the internals of the cell.

You will still want to subclass UITableViewCell with the four buttons (call them button1, button2, button3, and button4 if you don't have better names). You can make all the connection is Interface Builder. This will only be needed for populating object data into the cell during -tableView:cellForRowAtIndexPath:.

Upvotes: 0

c0ming
c0ming

Reputation: 3543

You can subclass UIButton with two properties row and column and implement the logic below:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"
                                                        forIndexPath:indexPath];

    MyButton *button1 = (MyButton *)[cell viewWithTag:1];
    button1.row = indexPath.row;
    button1.column = 1; // view tag
   [button1 addTarget:self
            action:@selector(clickAction:)
  forControlEvents:UIControlEventTouchUpInside];

    // button2,button3...

    return cell;
}

-(void)clickAction:(MyButton *)sender {
   // now you can known which button
   NSLog(@"%ld %ld", (long)sender.row, (long)sender.column); 
}

Upvotes: 2

CW0007007
CW0007007

Reputation: 5681

Several options here. But I would do the following:

Adopt a Delegate Protocol in your custom cell class (see here: How to declare events and delegates in Objective-C?) . This will handle the target selector for the buttons. Pass this message back to your view controller with the sender. To detect which cell it was in do the following:

CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
CGRect senderFrame = CGRectMake(buttonPosition.x, buttonPosition.y, sender.frame.size.width, sender.frame.size.height);

From here you can decide what the do. Use the buttons .x coordinate to determine which button it was or specify a different tag for each button in cellForRowAtIndexPath:. Or if you want to grab the index path of the cell you can do:

NSArray *indexPaths = [YOUR_TABLE_VIEW indexPathsForRowsInRect:senderFrame];
NSIndexPath *currentIndexPath = [indexPaths lastObject];

Upvotes: 0

2intor
2intor

Reputation: 1044

If those four views are UIButton then you will receive the tap events on each button or if they are not UIButton then you should add UITapGestureReconiser on each of this views

Upvotes: 0

Related Questions