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;
}
Related
I have 2 buttons in UITableview. When i select one button in my UITableView other buttons lower down are also clicked. Why is this clicking one button selecting multiple UIButtons?
I am using storyboard and my code is:
- (IBAction)yesBtnAction:(id)sender {
UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
NSIndexPath *indexPath = [self.take5table indexPathForCell:clickedCell];
UIButton *yesBTN=(UIButton *)[clickedCell.contentView viewWithTag:100];
UIButton *noBTN=(UIButton *)[clickedCell.contentView viewWithTag:111];
[yesBTN setBackgroundColor:[UIColor colorWithRed:0.23 green:0.62 blue:0.23 alpha:1.0]];
[noBTN setBackgroundColor:[UIColor whiteColor]];
}
- (IBAction)noBtnAction:(id)sender {
UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
NSIndexPath *indexPath = [self.take5table indexPathForCell:clickedCell];
UIButton *yesBTN=(UIButton *)[clickedCell.contentView viewWithTag:100];
UIButton *noBTN=(UIButton *)[clickedCell.contentView viewWithTag:111];
[yesBTN setBackgroundColor:[UIColor whiteColor]];
[noBTN setBackgroundColor:[UIColor redColor]];
}
Any help/suggestions would be greatly appreciated.
The problem is that you are not properly handling cell reuse.
You need to make sure your cellForRowAtIndexPath properly sets the state of each button. This needs to be based on some state you keep track of in your data model. This data model needs to be updated as each button it clicked.
You are attempting to keep the state in the button. That doesn't work.
I don't think its because of the same function being called.
Your problem is owing to dequeuing of the cells. Example:
In the first cell, when you click a button, you are changing the yesBTN to another color. So when the cell is re-used you are using that same cell with the changed color. What you could try is:
Add the indexPath.row of all the buttons clicked to a NSMutableArray and in your cellForRowAtIndexPath check if the array contains that particular indexPath.row
I think this code is helpful for u
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *Cell = [tableView dequeueReusableCellWithIdentifier:#"" forIndexPath:indexPath];
[cell.buttnName1 addTarget:self action:#selector(BtnAction:) forControlEvents:UIControlEventTouchUpInside];
[cell.buttnName2 addTarget:self action:#selector(BtnAction:) forControlEvents:UIControlEventTouchUpInside];
return Cell;
}
//action for button
- (void)BtnAction:(id)sender//type 1 button action
{
UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
UIButton *button = sender;
for (button in clickedCell.ButtonArr)//button create IBOtletcollections
{
[button setSelected:([button isEqual:sender])?YES:NO];
if ([button isSelected]) {
[button setBackgroundImage:[UIImage imageNamed:#"select_toggle"] forState:UIControlStateSelected];
tagType = button.tag;
//use local variable Tagtype(NsIntiger)
}else{
[button setBackgroundImage:[UIImage imageNamed:#"unselect_toggle"] forState:UIControlStateNormal];
}
}
}
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) {
....
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?
I am wondering whether someone has had similar problem that I am experimented lately.
Let me describe you a little bit further about my issue. I have got a UITableViewController where I have designed a set of custom UITableViewCell in the IB. Each UITableViewCell has got different elements like a UIButton, UITextField and UILabel, etc. Obviously, each UITableViewCell has a different identifier.
Once defined all my UITableViewCells, next step is to instantiate the cell on tableView:cellForRowAtIndexPath:. What I do in this method is depending on section and row, I instantiate the different UITableViewCells by dequeueReusableCellWithIdentifier:forIndexPath:.
Everything seems to work perfectly and correctly created on the table, but the problem arrives when I scroll down. At the very top, I have got a UITableViewCell with a UIButton that I have specified with a concrete action to perform when it is clicked. Scrolling down, there are a couple of UITableViewCells with the same format (an UIButton inside with different actions specified).
The problem is, when a click the first button from the bottom side, this button performs the first action that I have defined on the very top UIButton and its own action.
It seems that when uitableviewdelegate creates new cells or reuse them, it nests functionalities from other indexPath instead of the specified indexPath....
Hope that I have explained myself properly.
Thank you in advance.
[EDIT]
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [[UITableViewCell alloc] init];
NSLog(#"indexpath value is: %#", indexPath);
if (indexPath.section == 0)
cell = [self buildSection_0:tableView getIndexPath:indexPath];
else if (indexPath.section == 1)
cell = [self buildSection_1:tableView getIndexPath:indexPath];
else if (indexPath.section == 2)
cell = [self buildSection_2:tableView getIndexPath:indexPath];
else if (indexPath.section == 3)
cell = [self buildSection_3:tableView getIndexPath:indexPath];
else if (indexPath.section == 4)
cell = [self buildSection_4:tableView getIndexPath:indexPath];
return cell;
}
-(UITableViewCell *)buildSection_0:(UITableView *)tableView getIndexPath:(NSIndexPath *)indexPath{
NSString *identifier;
UITableViewCell *cell;
if (indexPath.row == 0){
identifier = #"headerCell";
cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
}
else if (indexPath.row == 1){
identifier = #"buttonCell";
cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
UIButton *button = (UIButton *)[cell viewWithTag:3001];
[button setTitle:#"Scan" forState:UIControlStateNormal];
[button addTarget:self action:#selector(scan) forControlEvents:UIControlEventTouchUpInside];
}
return cell;
}
-(UITableViewCell *)buildSection_3:(UITableView *)tableView getIndexPath:(NSIndexPath *)indexPath{
NSString *identifier;
UITableViewCell *cell;
if (indexPath.row == [[job jobLocations] count]) { // Adding button insert more Locations
identifier = #"buttonCell";
cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
UIButton *button = (UIButton *)[cell viewWithTag:3001];
[button setTitle:#"Add Location" forState:UIControlStateNormal];
[button addTarget:self action:#selector(newLocation) forControlEvents:UIControlEventTouchUpInside];
}
else{ // Showing different Locations
identifier = #"locationCell";
cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
UILabel *label = (UILabel *)[cell viewWithTag:3007];
UITextField *textField = (UITextField *)[cell viewWithTag:3008];
[label setText:[NSString stringWithFormat:#"Location #%ld", (long)indexPath.row + 1]];
[textField setText:[[[job jobLocations] objectAtIndex:indexPath.row] locationType]];
}
return cell;
}
dequeueReusableCellWithIdentifier:forIndexPath will reuse cells that are no longer visible, you may want to implement prepareForReuse on your cell, or reset it after you dequeue it.
[EDIT]
Seeing you added your code, you should remove previous target/actions from the button before adding the new ones, as the old one will still be there if a cell is being reused:
[button removeTarget:self action:NULL forControlEvents:UIControlEventTouchUpInside];
[button addTarget:...
Add a tag to the button that corresponds to the indexPath.row. Then in your button action method, you can determine which button was clicked by looking at the (UIButton *)sender's tag.
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.