Differentiate button and row click event in Table View - ios

In my Application in UITableView i put UIButton in every cell.
but when i click on Button then some times button event will come and some time Cell's Row clicked.
I need to get button click event all the time when button click and row click event when row clicked.
How to come over this issue ?
All the Row are identical (means i didn't use reusable).
My code for this is...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"Cell1"];
[cell clearsContextBeforeDrawing];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btn.frame = CGRectMake(5,220,70, 30);
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal ];
[btn setTitle:[NSString stringWithFormat:#"button == %ld",(long)indexPath.row] forState:UIControlStateNormal];
btn.tag = indexPath.row;
[btn addTarget:self action:#selector(methodOfButton:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:btn];
}
return cell;
}
Any tutorial, code, link will be great help.

Most likely the problem you're seeing happen when user tapped close to the button but not on it. In other interface elements you will not notice this, since no other event will be generated but in your cases 'didSelectRow' will fire.
You need to make sure button occupies as much room as possible on the cell (at least full cell height). This way you will get user less chance to miss tap. Right now your button is only 30px tall, what the height of the cell?

Related

UITableview not updating uitableviewcell custom cell from storyboard

I have tried as many as solution I found to resolve this problem.
My latest code is show blow:
static NSString *simpleTableIdentifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
[[cell viewWithTag:1000] removeFromSuperview];
[[cell viewWithTag:2000] removeFromSuperview];
// other code for setting view that works perfect then
if(some condition){
UIButton *btn=[UIButton buttonWithType:UIButtonTypeCustom];
[btn setImage:[UIImage imageNamed:#"upgrey.png"] forState:UIControlStateNormal];
btn.frame=upButton.frame;
[btn addTarget:self action:#selector(upButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:btn];
UIButton *btn2=[UIButton buttonWithType:UIButtonTypeCustom];
[btn2 setImage:[UIImage imageNamed:#"downgrey.png"] forState:UIControlStateNormal];
btn2.frame=downButton.frame;
[btn2 addTarget:self action:#selector(downButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:btn2];
btn.tag=1000;
btn2.tag=2000;
}
return cell;
but this does not work. if i add
[[cell viewWithTag:1000] removeFromSuperview];
[[cell viewWithTag:2000] removeFromSuperview];
in start it remove buttons from all cells. and if i do not use this. it shows all cells with these two buttons.
thanks in advance.
Even though you've already accepted your own answer, I'll take a moment to explain why you had that issue and how you can better structure your UITableViews in the future.
The reason why "it shows all cells with these two buttons" when you don't have those removeFromSubview lines is because by implementing dequeueReusableCellWithIdentifier:, you're reusing your cells and the contents thereof. Repeatedly adding the same subview during each call of cellForRowAtIndexPath makes the fact that you're reusing the cells completely pointless since you're reusing absolutely nothing within them. So either, don't reuse your cells at all or, better yet since each cell's contents are exactly the same, do reuse your cells and also reuse those buttons.
There are two good ways to do this. One way to do this is to create a UITableViewCell custom subclass. But since your cells' contents are fairly simple and you say your else conditional's already functional, I'll suggest a different way that might be a bit less code-intensive. Here's my suggestion:
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Create two identifiers -- one for "some condition" one
// for the "other condition"
static NSString *simpleTableIdentifier1 = #"cell1";
static NSString *simpleTableIdentifier2 = #"cell2";
if (some condition) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier1];
// Only add the buttons when cell == nil, i.e. during the first
// time cell's initialized to hold a UITableViewCell
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier1]
UIButton *btn=[UIButton buttonWithType:UIButtonTypeCustom];
[btn setImage:[UIImage imageNamed:#"upgrey.png"] forState:UIControlStateNormal];
btn.frame=upButton.frame;
[btn addTarget:self action:#selector(upButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:btn];
UIButton *btn2=[UIButton buttonWithType:UIButtonTypeCustom];
[btn2 setImage:[UIImage imageNamed:#"downgrey.png"] forState:UIControlStateNormal];
btn2.frame=downButton.frame;
[btn2 addTarget:self action:#selector(downButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:btn2];
}
} else {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier2];
if (cell == nil) {
....

Button added to tableview cell appears on other random cells as well

I'm experiencing a problem with regards to adding a button to a tableview cell. Basically, I'm adding a button to a specific cell, but that button also appears on random other cells (not currently in the view).
When I tap a cell, I set a variable (tappedCell=indexPath), and reload that cell. In my cellForRowAtIndexPath I check if the indexPath is equal to the tappedCell, and if it is I add a button to it. That works, but this button also appears on other cells further down in the tableView. These cells are not in the view when I tap it, but further down, so I have to scroll to see them. If I keep scrolling up and down (quickly), more and more cells with buttons appears. This seems to be completely random.
I've tried adding a NSLog() inside the if-statement where I add the button, but this is not called more than once (when I tap the original cell). So it's actually not adding more buttons, it has to be the same one appearing in multiple cells.
I have no idea why this is happening. I've tried adding a button to every cell, and then just set hidden = YES as standard, and hidden = NO when the [self.tappedCell isEqual:indexPath], but this does not change anything...
Here you can see the code I use to add the button to my cell:
UIButton *newBtn=[UIButton buttonWithType:UIButtonTypeCustom];
[newBtn setImage:[UIImage imageNamed:#"open.png"] forState:UIControlStateNormal];
[newBtn setFrame:CGRectMake(220, 0, 100, 86)];
[newBtn addTarget:self action:#selector(openImage:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:newBtn];
I don't know what I'm doing wrong, or if I am doing something wrong at all. It's worth mentioning that I'm using a standard Apple/iOS style=subtitle cell, and not a custom one. This is to keep it consistent throughout my app (The cells are made in the storyboard, and I'm reusing a prototype cell).
Could anyone give me any insight on why it's behaving like it does, and how I can fix it?
Thanks in advance!
EDIT 1:
Here's my full code for cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Set up the item
XYZItem *item = [self.itemsInView objectAtIndex:indexPath.row];
NSString *CellIdentifier = #"itemPrototypeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.text = item.itemName;
if ([self.selectedItem isEqual:indexPath])
{
cell.textLabel.numberOfLines = 2;
UIButton *newBtn=[UIButton buttonWithType:UIButtonTypeCustom];
[newBtn setImage:[UIImage imageNamed:#"open.png"] forState:UIControlStateNormal];
[newBtn setFrame:CGRectMake(220, 0, 100, 86)];
[newBtn addTarget:self action:#selector(openImage:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:newBtn];
}
NSString *detailTextLabelText = [NSString stringWithFormat:#"%lu subitems", (unsigned long)[item.subItems count]];
cell.detailTextLabel.text = detailTextLabelText;
return cell;
}
The button is showing up on cells that have reused a button that previously added it.
There are a few options here.
Make the button part of every cell. Set it's .hidden property every time you create/reuse one to the appropriate value.
Remove the button in the cell's prepareForReuse method.
Assuming self.tappedCell is a UITableViewCell subclass and indexPath is an index path, [self.tappedCell isEqual:indexPath] will always return NO
I faced same issue and i also had multiple headers:
The solution is:
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
headerLabel = [[UILabel alloc] initWithFrame:
CGRectMake(15, 5, self.tableView.frame.size.width, 15.0)];
headerLabel.backgroundColor = [UIColor clearColor];
headerLabel.textAlignment = NSTextAlignmentLeft;
[headerLabel setFont:[UIFont fontWithName:#"Helvetica Neue Medium" size:13.0]];
headerLabel.tag = 1002;
[cell.contentView addSubview:headerLabel];
button = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, HEIGHTROW)];
[button addTarget:self action:#selector(eventAddAlert) forControlEvents:UIControlEventTouchUpInside];
[button setBackgroundImage:[UIImage imageNamed:#"add_manually.png"] forState:UIControlStateNormal];
button.hidden = YES;
button.tag = 1001;
[cell.contentView addSubview:button];
}else{
// use viewWithTag to find lblNombre in the re-usable cell.contentView
headerLabel = (UILabel *)[cell.contentView viewWithTag:1002];
button = (UIButton *)[cell.contentView viewWithTag:1001];
}
if(indexPath.section == 0 && indexPath.row == 0){
button.hidden = NO;
}else{
button.hidden = YES;
.........
}
.........

iOS - Long Press Gesture Recognizer for UIButton in UITableViewCell

I have a UITableViewCell with 3 UIButtons in it used to decrement a counter. On long press one of the buttons, I would like to set the counter to 0.
Doing this in Interface Builder, I dragged a Long Press Gesture Recognizer onto my button and connected the selector to an IBAction specified in my UITableViewCell.m.
That's all I did but when I run the app it gives the following error.
'NSInternalInconsistencyException',
reason: 'invalid nib registered for identifier (editQuotaCell)
- nib must contain exactly one top level object which must be a UITableViewCell instance'
Am I missing any steps?
Just create the button in coding wise add the gesture and selector. It will work. Check the bellow code
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyCell"];
if(cell == nil)
{
UITableViewCell *cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"MyCell"];
cell.backgroundColor = [UIColor clearColor];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn addTarget:self action:#selector(select_Action:) forControlEvents:UIControlEventTouchUpInside];
[btn setTag:Selection_Tag];
[btn setBackgroundImage:[UIImage imageNamed:#"Demo03.png"] forState:UIControlStateNormal];
[btn setFrame:CGRectMake(0,0,tableView.frame.size.width,tableView.rowHeight)];
[cell.contentView btn];
//Add Your gestures here
}
return cell;
}

UIButton Image Changing In Multiple UITableViewCells

I am currently building an iOS app that uses a UITableView with custom UITableViewCells that include a button. What I want to accomplish is having the UIButton's image change on touch up inside. That part is working fine, the issue is when you scroll, suddenly every few row buttons located in the UITableViewCell have this new image. Below is some of my code. Any help would be appreciated.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *cellID = #"CellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellID];
//YEP BUT
yepBut = [[UIButton alloc] initWithFrame:CGRectMake(23, 16, 64, 32.5)];
[yepBut addTarget:self action:#selector(yepPost:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:yepBut];
}
[yepBut setImage:[UIImage imageNamed:#"yepBtn"] forState:UIControlStateNormal];
return cell;
}
-(void) yepPost:(id) sender{
//UIButton *clicked = (UIButton *) sender;
[sender setImage:[UIImage imageNamed:#"yepBtnACT"] forState:UIControlStateNormal];
}
Thanks!
You need to have a property that you set when the button is touched that you check in cellForRowAtIndexPath. So, in the button's method, have a property, say lastTouched (#property (strong,nonatomic) NSIndexPath *lastTouched, or NSInteger if you don't have sections), that you set to the indexPath (or indexPath.row) of the cell in which the touched button resided (gotten from the cell which you get by searching up through the superviews of the button until you find the cell, or by using tags on your buttons equal to the indexPath.row). After that, you have to reloadData or reloadRowsAtIndexPaths to make the change happen. In cellForRowAtIndexPath, you would set your images like this:
if ([self.lastTouched isEqual:indexPath]) {
[yepBut setImage:[UIImage imageNamed:#"yepBtnACT"] forState:UIControlStateNormal];
else{
[yepBut setImage:[UIImage imageNamed:#"yepBtn"] forState:UIControlStateNormal];
}
When you first initialize lastTouched, you want to set it to an indexPath that doesn't occur in your table, so nothing will have the yepButAct image until you touch one.
Subclassing UITableViewCell fixed my issue.

IOS: Maintaining button state in uitableviewcell

I have an iPhone app problem that's been bugging me for a few days and it really doesn't seem like it should be this difficult so I'm sure I'm missing something obvious. I have researched plenty of forum discussions on "similar" topics but nothing that actually addresses this issue, specifically.
To be clear, if there is some piece of documentation or some other source that I should research, please point me in the right direction.
Here goes...
I have a list of items that I display to the user within a table (uitableview). The cell (uitableviewcell) for each item is custom and contains an image and a Like button (uibutton). As expected, for each item in the the table, the user can click the Like button or ignore it. The like button calls a separate process to update the server. Simple, right?
So, here is the issue:
When the Like button is clicked on a particular cell, the Selected state works fine but the moment you scroll the cell out of view, other random cells in the table show the Like button as Selected even though they were never touched. Because the cells are reused, for obvious performance reasons, I understand why this could happen. What I don't understand is why my approach (see code below) would not override or reset the button's state the way I think it should. For brevity, I am only including the relevant code here (hopefully formatted properly):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyCustomCell";
MyCustomViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[MyCustomViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
NSString *myRating = [[self.dataArray objectAtIndex:indexPath.row] valueForKey:#"my_rating"];
// Create the Like button
UIButton *likeButton = [[UIButton alloc] initWithFrame:CGRectMake(260, 68, 40, 40)];
[likeButton setImage:[UIImage imageNamed:#"thumbsUp"] forState:UIControlStateNormal];
[likeButton setImage:[UIImage imageNamed:#"thumbsUpSelected"] forState:UIControlStateSelected];
if (myRating == #"9") {
[likeButton setSelected:YES];
}
[likeButton setTitle:#"9" forState:UIControlStateNormal];
[likeButton setTag:indexPath.row];
[cell.contentView addSubview:likeButton];
[likeButton addTarget:self action:#selector(likeButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
- (void)likeButtonPressed:(UIButton *)sender {
// Changed the Selected state on the button
UIButton *button = (UIButton *)sender;
button.selected = !button.selected;
// Create a new object with the user's rating and then replace it in the dataArray
NSString *ratingText = sender.titleLabel.text;
NSMutableArray *myMutableArray = [[self.dataArray objectAtIndex:row] mutableCopy];
[myMutableArray setValue:ratingText forKey:#"my_rating"];
[self.dataArray replaceObjectAtIndex:row withObject:myMutableArray];
}
So, I've been through many iterations of this but I can't seem to get the state of the button to show the Selected image for those items that are Liked and keep the normal image for those items that have not been Liked.
Any help or advice would be greatly appreciated.
there is a simple way out of this. you can trick the button to keep being in the latest selected state.
make a mutable array, for the purpose of keeping the selected state of the button
selectedButton = [[NSMutableArray alloc]init];//i've already defined the array at the .h file
for (int i = 0; i<yourTableSize; i++) //yourTableSize = how many rows u got
{
[selectedButton addObject:#"NO"];
}
at the tableviewcell method, declare your button and set it so that it refers to the mutablearray to set it's selected state
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
}
UIImage *img = [UIImage imageNamed:#"btn_unselected.png"];
UIButton *toggleButton = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, img.size.width, img.size.height)];
[toggleButton setImage:img forState:UIControlStateNormal];
img = [UIImage imageNamed:#"btn_selected.png"];
[toggleButton setImage:img forState:UIControlStateSelected];
[toggleButton setTag:indexPath.row+100];//set the tag whichever way you wanted it, i set it this way so that the button will have tags that is corresponding with the table's indexpath.row
[toggleButton addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:toggleButton];
//and now we set the button's selected state, everytime the table reuse/redraw the cell the button will set it's selected state according to the array
if([[selectedButton objectAtIndex:indexPath.row]isEqualToString:#"NO"])
{
[toggleButton setSelected:NO];
}
else
{
[toggleButton setSelected:YES];
}
return cell;
and finally, create the method which the button triggered when the button is pressed, to change it's selected state
-(void)buttonPressed:(UIButton*)sender
{
int x = sender.tag - 100; //get the table's row
if([sender isSelected]) //if the button is selected, deselect it, and then replace the "YES" in the array with "NO"
{
[selectedButton replaceObjectAtIndex:x withObject:#"NO"];
[sender setSelected:NO];
}
else if (![sender isSelected]) //if the button is unselected, select it, and then replace the "NO" in the array with "YES"
{
[selectedButton replaceObjectAtIndex:x withObject:#"YES"];
[sender setSelected:YES];
}
}
The problem is that every time you create or reuse a cell you're giving it a new like button, so when you reuse a cell where the like button has been activated, you're giving it a deactivated like button but the old, activated like button is still there as well.
Instead of creating a like button every time you need a cell, you should just be setting the state of an existing like button. See the answers to this question for some possible ways of handling that.
This is not valid (At least not if you are trying to compare strings my contents instead of addresses):
if (myRating == #"9")
Try this:
if ([myRating isEqualToString:#"9"])
And +1 to yuji for noticing the multiple button creation.

Resources