I have a UITableview that I'm displaying some tasks and each row has a checkbox to mark tasks complete or not.
I'm looking to toggle the checkmark when the user taps on the checkbox, and switch to a detailed view when the user taps on the row. The latter is easy, just by using
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
However, I wanted to separate the selection areas, toggling only the checkbox when the accessoryview is selected, and going into detailed view only when the rest of the cell is selected. If I add a UIbutton inside the accessoryview, the user would be selecting the row AND the UIButton when they only wanted to hit the checkbox, right?
Also, what if the user was just scrolling through the tableview by dragging along the accessoryview? Wouldn't this trigger an action on the UIButton on TouchUp?
Anyone have any ideas on how to do this? Thanks for your time!
How about managing accessory taps inside this delegate method:
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
EDIT:
You can do something like this for custom accessoryView that responds to accessoryButtonTappedForRowWithIndexPath: method.
In cellForRowAtIndexPath: Method -
BOOL checked = [[item objectForKey:#"checked"] boolValue];
UIImage *image = (checked) ? [UIImage imageNamed:#"checked.png"] : [UIImage imageNamed:#"unchecked.png"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
CGRect frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
button.frame = frame;
[button setBackgroundImage:image forState:UIControlStateNormal];
[button addTarget:self action:#selector(checkButtonTapped:event:) forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor = [UIColor clearColor];
cell.accessoryView = button;
- (void)checkButtonTapped:(id)sender event:(id)event
{
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];
if (indexPath != nil)
{
[self tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];
}
}
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
NSMutableDictionary *item = [dataArray objectAtIndex:indexPath.row];
BOOL checked = [[item objectForKey:#"checked"] boolValue];
[item setObject:[NSNumber numberWithBool:!checked] forKey:#"checked"];
UITableViewCell *cell = [item objectForKey:#"cell"];
UIButton *button = (UIButton *)cell.accessoryView;
UIImage *newImage = (checked) ? [UIImage imageNamed:#"unchecked.png"] : [UIImage imageNamed:#"checked.png"];
[button setBackgroundImage:newImage forState:UIControlStateNormal];
}
Related
I have an application based on CoreDataBooks. I want to toggle two images in the cell accessory button as noted in the iOS sample code, but am running into a roadblock. I cannot figure out how to obtain the UITableViewCell that is noted in the accessoryButtonTappedForRowWithIndexPath: method. The sample stored the object in the item as key-value, but I cannot figure out how to do this with my patient and core data.
First of all, here is the relevant code from the Accessory sample:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *kCustomCellID = #"MyCellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCustomCellID];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCustomCellID] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
}
NSMutableDictionary *item = [dataArray objectAtIndex:indexPath.row];
cell.textLabel.text = [item objectForKey:#"text"];
[item setObject:cell forKey:#"cell"];
BOOL checked = [[item objectForKey:#"checked"] boolValue];
UIImage *image = (checked) ? [UIImage imageNamed:#"checked.png"] : [UIImage imageNamed:#"unchecked.png"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
CGRect frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
button.frame = frame; // match the button's size with the image size
[button setBackgroundImage:image forState:UIControlStateNormal];
// set the button's target to this table view controller so we can interpret touch events and map that to a NSIndexSet
[button addTarget:self action:#selector(checkButtonTapped:event:) forControlEvents:UIControlEventTouchUpInside];
cell.backgroundColor = [UIColor clearColor];
cell.accessoryView = button;
return cell;
}
- (void)checkButtonTapped:(id)sender event:(id)event
{
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];
if (indexPath != nil)
{
[self tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];
}
}
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
NSMutableDictionary *item = [dataArray objectAtIndex:indexPath.row];
BOOL checked = [[item objectForKey:#"checked"] boolValue];
[item setObject:[NSNumber numberWithBool:!checked] forKey:#"checked"];
UITableViewCell *cell = [item objectForKey:#"cell"];
UIButton *button = (UIButton *)cell.accessoryView;
UIImage *newImage = (checked) ? [UIImage imageNamed:#"unchecked.png"] : [UIImage imageNamed:#"checked.png"];
[button setBackgroundImage:newImage forState:UIControlStateNormal];
}
I am able to set my image correctly by using a "check" property of "Y" or "N" that I can store in CoreData, as noted below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Patient *patient = [fetchedResultsController_ objectAtIndexPath:indexPath];
cell.textLabel.text = patient.name;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
UIImage *image = [UIImage imageNamed:#"unchecked.png"];
if ([patient.check isEqualToString: #"Y"] == YES)
image = [UIImage imageNamed:#"checked.png"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
CGRect frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
button.frame = frame; // match the button's size with the image size
[button setBackgroundImage:image forState:UIControlStateNormal];
// set the button's target to this table view controller so we can interpret touch events and map that to a NSIndexSet
[button addTarget:self action:#selector(checkButtonTapped:event:) forControlEvents:UIControlEventTouchUpInside];
cell.backgroundColor = [UIColor clearColor];
cell.accessoryView = button;
}
However, I am having problems with this method, as noted in the code:
- (void) tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
Patient *patient = [fetchedResultsController_ objectAtIndexPath:indexPath];
// HOW CAN I OBTAIN THE UITABLEVIEWCELL?
UITableViewCell *cell = [item objectForKey:#"cell"];
UIButton *button = (UIButton *)cell.accessoryView;
UIImage *newImage = (checked) ? [UIImage imageNamed:#"unchecked.png"] : [UIImage imageNamed:#"checked.png"];
[button setBackgroundImage:newImage forState:UIControlStateNormal];
}
I appreciate any assistance.
AMENDMENT: Thanks to BoltClock, this all came together. Posting the final code in case anyone else wants to use this methodology.
- (void) tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
Patient *patient = [fetchedResultsController_ objectAtIndexPath:indexPath];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UIButton *button = (UIButton *)cell.accessoryView;
UIImage *image = [UIImage imageNamed:#"checked.png"];
// patient.check is a string value set to either "Y" or "N"
// This allows the check mark to be permanently saved
if ([patient.check isEqualToString: #"Y"] == YES) {
image = [UIImage imageNamed:#"unchecked.png"];
patient.check = #"N";
}
else {
// image defaults to checked.png
// image = [UIImage imageNamed:#"checked.png"];
patient.check = #"Y";
}
[button setBackgroundImage:image forState:UIControlStateNormal];
// Save patient.check value
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
Use the table view's cellForRowAtIndexPath: method and pass in the index path as the argument.
- (void) tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
Patient *patient = [fetchedResultsController_ objectAtIndexPath:indexPath];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UIButton *button = (UIButton *)cell.accessoryView;
UIImage *newImage = (checked) ? [UIImage imageNamed:#"unchecked.png"] : [UIImage imageNamed:#"checked.png"];
[button setBackgroundImage:newImage forState:UIControlStateNormal];
}
i have a table view, in one condition i want to use an accessory using UITableViewCellAccessoryDetailDisclosureButton, but i want to change that icon with my icon, can i do it??
yes ....you can do it easily by adding custom button...
Create the Custom button,
Setting the image of button
Add this button to accessoryView
Set the method for the button
I am providing you some help with the Code:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
cell =(Custom *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[Custom alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
[cell setBackgroundColor:[UIColor clearColor]];
menuTable.separatorColor =[UIColor clearColor];
}
UIImage *image = [UIImage imageNamed:#"ButtonClickOrder.png"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
CGRect frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
button.frame = frame; // match the button's size with the image size
//[button setBackgroundImage:image forState:UIControlStateNormal];
[button setImage:image forState:UIControlStateNormal];
// set the button's target to this table view controller so we can interpret touch events and map that to a NSIndexSet
[button addTarget:self action:#selector(checkButtonTapped:event:) forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor = [UIColor clearColor];
cell.accessoryView = button;
return cell;
}
//These are the methods which are called with the AccessoryView is Clicked
- (void)checkButtonTapped:(id)sender event:(id)event
{
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:menuTable];
NSIndexPath *indexPath = [menuTable indexPathForRowAtPoint: currentTouchPosition];
NSLog(#"Section:%d",indexPath.section);
NSLog(#"Index:%d",indexPath.row);
if (indexPath != nil)
{
[ self tableView: menuTable accessoryButtonTappedForRowWithIndexPath: indexPath];
}
}
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"%d",indexPath.section);
}
Hope this will surely work for u....
I have this code on my cellForRowAtindexPath, with a custom cell and a button on the each cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"tblCellView";
TableCellView *cell = (TableCellView *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
Alarms *alarma = (Alarms *)[alarmsArray objectAtIndex: indexPath.row];
if (!cell) {
[[NSBundle mainBundle] loadNibNamed:#"TableCellView" owner:self options:nil];
cell = tblCell;
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(-7, 4, 30, 33)];
[button addTarget:self action:#selector(favButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[button setTag:1];
[cell.contentView addSubview:button];
[button release];
}
// Set up the cell.
[cell setLabelText: [alarma nombreAlarma]];
UIButton *button = (UIButton *)[cell viewWithTag:1];
button.tag = indexPath.row;
return cell;
}
My problem is that when I click on the button, I got random results, as soon as I move on the table and cells are reusing I got different indexes that are not equal to label tex for the cell in question.
-(IBAction)favButtonAction: (id)sender
{
UIButton *button = (UIButton *)sender;
Alarms *alarma = (Alarms *)[alarmsArray objectAtIndex: button.tag];
NSLog(#"labelText: %#",[alarma nombreAlarma]);
}
for example, the first cell always is ok but the last cell is equal to first objectAtIndex (maybe button.tag is equal to 0 when it must be 14?)
Ok I found a solution, is a different approach but it works :
[button addTarget:self action:#selector(checkButtonTapped:event:) forControlEvents:UIControlEventTouchUpInside];
and then on the button handler:
- (void)checkButtonTapped:(id)sender event:(id)event
{
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];
if (indexPath != nil)
{
...
}
}
more info here Detecting which UIButton was pressed in a UITableView
Don't put tag value for button inside cell.
This is my button inside the content view of my cell. Note: button should be subview of Contentview.
[cell1.YourButton addTarget:self action:#selector(OnClickCategory:) forControlEvents:UIControlEventTouchUpInside];
Action for my button
- (void) OnClickCategory:(id) sender {
//NSIndexPath *indexPath =(NSIndexPath *)[sender tag];
UIView *senderButton = (UIView*) sender;
NSIndexPath *indexPath = [CategoryTable indexPathForCell: (UITableViewCell*)[[senderButton superview]superview]];
NSLog(#"%d",indexPath.row);
}
This works for me.
I currently have a button defined in a cell and a method to track its UITouchDown action as shown:
- (void) clickedCallSign:(id)sender {
int index = [sender tag];
NSLog(#"event triggered %#",index);
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
//Callsign button
UIButton *button;
CGRect rect = CGRectMake(TEXT_OFFSET_X, BORDER_WIDTH, LABEL_WIDTH, LABEL_HEIGHT);
button = [[UIButton alloc] initWithFrame:rect];
cell.tag=[indexPath row];
button.tag=[indexPath row];
[button addTarget:self action:#selector(clickedCallSign:) forControlEvents:UIControlEventTouchDown];
[button setBackgroundColor:[UIColor redColor]];
[button setTitle:#"hello" forState:UIControlStateNormal];
[cell.contentView addSubview:button];
[button release];
}
However when I click a cell in the simulator, the console debug message is: "event triggered (null)" and my app crashes shortly after.
How can I correctly get the indexPath.row value into my clickedCallSign method?
First off, index is an int, so your NSLog needs to look like this (note the %d):
NSLog(#"event triggered %d", index);
(It's possible that this leads to a crash, but it's also likely that something else entirely is going that causes the instability.)
Tag is good until you haven't both sections and rows. Try another way to get index path:
- (void)tableView:(UITableView*)tableView willDisplayCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexPath {
//...
[button addTarget:self action:#selector(clickedCallSign:withEvent:) forControlEvents:UIControlEventTouchDown];
//...
}
// Get the index path of the cell, where the button was pressed
- (NSIndexPath*)indexPathForEvent:(id)event
{
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
return [self.tableView indexPathForRowAtPoint:currentTouchPosition];
}
- (IBAction)clickedCallSign:(id)sender withEvent:(UIEvent*)event
{
NSIndexPath* buttonIndexPath = [self indexPathForEvent:event];
}
If you don't want to use the tag field, have the button invoke this method:
- (void)tapAccessoryButton:(UIButton *)sender
{
UIView *parentView = sender.superview;
// the loop should take care of any changes in the view heirarchy, whether from
// changes we make or apple makes.
while (![parentView.class isSubclassOfClass:UITableViewCell.class])
parentView = parentView.superview;
if ([parentView.class isSubclassOfClass:UITableViewCell.class]) {
UITableViewCell *cell = (UITableViewCell *) parentView;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
[self tableView:self.tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
}
}
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btn.frame = CGRectMake(230.0f, 4.0f, 60.0f, 36.0f);
[btn setTitle:#"OK" forState:UIControlStateNormal];
[btn setTitle:#"OK" forState:UIControlStateSelected];
[btn addTarget:self action:#selector(onClickRename:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:btn];
return cell;
}
but if the user selects the button on particular row, i can change that image through onClickRename...but can i get inwhich row's image has been touched through
(void)tableView:(UITableView )tableView didSelectRowAtIndexPath:(NSIndexPath)indexPath ?
you could do something like
[btn setTag:[indexPath] row]
in your cell setup, and then
- (void) onClickRename:(id)sender {
int row = sender.tag;
}
you'd know which table row was hit.
Apple uses the following technique in their Accessory sample code:
- (void)checkButtonTapped:(id)sender event:(id)event
{
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];
if (indexPath != nil)
{
// do what you want with the item at indexPath
}
}
In Swift, you can set a tag for each button
Also, set each index value as its tag, we get to know which button is clicked at which row.
to set tag, in cellForAtIndexPath
cell.button.tag = indexPath.row
in button Action
#IBAction func anAction(_sender : AnyObject){
let tappedButton = sender.tag // gives the row and button
}