Reputation: 81
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
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 IBAction
s 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
Reputation: 1977
Generalized undetailed answer:
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
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
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
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
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
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