I have an UISwitch in my UITableViewCell prototype.
The problem is, when I turn on one of the switches, one of the not shown switches also gets turned on.
This doesn't happen in the iPad version where all cells are shown.
Here is some code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
Notification *notification = [notifications objectAtIndex:indexPath.row];
UILabel *titleLabel = (UILabel *) [cell viewWithTag:100];
UISwitch *newsSwitch = (UISwitch *) [cell viewWithTag:101];
UIImageView *imageView = (UIImageView *) [cell viewWithTag:102];
[titleLabel setText:[notification name]];
[imageView setImage:[UIImage imageNamed:[notification image]]];
BOOL isOn = [storage boolForKey:[NSString stringWithFormat:#"notification_%#", [notification name]]];
[newsSwitch setOn:isOn];
[newsSwitch setTag:indexPath.row];
[newsSwitch addTarget:self action:#selector(didChangeStateForSwitch:) forControlEvents:UIControlEventValueChanged];
return cell;
}
Your problem is that you first query the switch via its tag:
UISwitch *newsSwitch = (UISwitch *) [cell viewWithTag:101];
But later on, you change that tag:
[newsSwitch setTag:indexPath.row];
So when the cell gets reused, the switch won't be found as now its tag isn't 101 any more. Therefor, the switch will be stuck in its old state.
You can easily verify this by adding a NSLog(#"Switch: %#", newsSwitch); after querying the switch. You'll see that it'll output Switch: (null) for those rows where you have "wrong" switch values.
The solution this is to not modify the tag.
Question is, how do you remember which row the switch is for, then? One way would be this:
- (void)didChangeStateForSwitch:(id)sender
{
NSIndexPath *indexPath = [myTableView indexPathForCell:[sender superview]];
...
}
Another possibility is to use associated objects.
First time when you load the cell you are taking the view with tag 101 for switch.Few lines after that you are setting new tag to this switch and next time when you try to take the view with tag 101 it doesn't exist.[newsSwitch setTag:indexPath.row]; delete this line and try again
You can get the index path as #DarkDust suggested
- (void)didChangeStateForSwitch:(id)sender
{
NSIndexPath *indexPath = [myTableView indexPathForCell:[sender superview]];
...
}
Related
I have the cells with identifiers in Storyboard and the respective objects with tags. The first time that cellForRowAtIndexPath is called, viewWIthTag returns the object ok. The next times the btnCompartilhar, for example, is nil.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *sectionTitle = [sectionsTitles objectAtIndex:indexPath.section];
NSArray *secAtt = [attractions objectForKey:sectionTitle];
Evento *evento = (Evento*)[secAtt objectAtIndex:indexPath.row];
UITableViewCell *cell = nil;
if(evento.listaImagens && [evento.listaImagens count] > 0) {
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
}
else {
cell = [tableView dequeueReusableCellWithIdentifier:#"CellSemFoto" forIndexPath:indexPath];
}
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
UIButton *btnCompartilhar = (UIButton *)[cell.contentView viewWithTag:40];
[btnCompartilhar.titleLabel setHidden:YES];
btnCompartilhar.titleLabel.text = sectionTitle;
btnCompartilhar.tag = indexPath.row;
[btnCompartilhar addTarget:self action:#selector(compartilharClick:) forControlEvents:UIControlEventTouchDown];
return cell;
}
I executed the command po [cell.contentView recursiveDescription] and discovered that the Button with tag 40 is there only at the first time. But all the others objects are all the times
Calling btnCompartilhar.tag = indexPath.row; changes the tag, so you can't find it again next time when the cell is reused.
You should not rely on viewWithTag:, instead you should create a custom UITableViewCell subclass and add properties which allow you to access all of the views you need. The cell can also then have additional information like the row number if you need it.
Note that your current approach has other issues, like adding the target and action multiple times, which is also not a great idea.
I am trying to change the color of a button when pressed that is in a tableviewCell. However my code changes the color of every button in the table and not just the one in the cell I selected,
How would I go about just changing the color of the button I pressed.
Please see my code below,
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UIButton *addNotesButton = (UIButton *)[cell viewWithTag:106];
[addNotesButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Test";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
UIButton *addNotesButton = (UIButton *)[cell viewWithTag:106];/
[addNotesButton addTarget:self action:#selector(addNotes :) forControlEvents:UIControlEventTouchUpInside];
}
The main issue might be in your cellForRowAtIndexPath: method. UITableView cells are re-used as they are displayed on the screen. dequeueReusableCellWithIdentifier: method returns a cell if it has been marked as ready for reuse. You must have seen this method of UITableView being used in cellForRowAtIndexPath: method. (See this link)
So in cellForRowAtIndexPath: you will have to configure each cell as it is being loaded or else it will display old values (since the cells are being reused).
You can either declare a property or a simple variable of type NSIndexPath.Let the variable be called _selectedIndexPath. Then in didSelectRowAtIndexPath: you can assign this property to the indexPath selected.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSArray *indexPaths = nil;
if (_selectedIndexPath) {
indexPaths = #[_selectedIndexPath, indexPath];
} else {
indexPaths = #[indexPath];
}
_selectedIndexPath = indexPath;
[tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
}
- (void)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Your Cell Identifier"];
UIButton *addNotesButton = (UIButton *)[cell viewWithTag:106];
if (indexPath.row == _selectedIndexPath.row) {
[addNotesButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
} else {
[addNotesButton setTitleColor:[UIColor clearColor] forState:UIControlStateNormal];
}
}
You don't need to change color manually in did select row at index path. Just set the color for UIControlStateSelected and on the action of button tap set the buttons selected property to YES. From your code i think this should work.
inside cell for row at index path method
[addNotesButton setTitleColor:[UIColor blueColor] forState:UIControlStateSelected];
and in button action method
-(IBAction)addNotes:(id)sender
{
UIButton *button = (UIButton*)sender;
buttons.selected = !button.isSelected;
}
I think this will work.
After trying everything and failed. I ended up having a hidden value in each row that would change when the button is pressed. So the code reads the value then configures the button for each row.
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.
This question already has answers here:
How to know the UITableview row number
(10 answers)
Closed 5 years ago.
I have a UITableview with multiple cells. On each cell I'm adding buttons dynamically according to some array count. So, when I click on a button I'm able to get the tag value of the button. But how to get the indexPath of that cell?
Here is my code in -cellForRowAtIndexPath:
UIView *view=(UIView*)[cell.contentView viewWithTag:indexPath.row+444];
UIImageView *img=(UIImageView*)[cell.contentView viewWithTag:indexPath.row+999];
img.image=[UIImage imageNamed:#"BHCS_empty.png"];
if(integer!=50)
{
NSInteger y_axis=0;
NSArray *Arr=[tableSubCategoryArr objectAtIndex:indexPath.row];
img.image=[UIImage imageNamed:#"BHCS_selected.png"];
view.Frame= CGRectMake(0,50,281, integer-50);
for (int i=0; i<[Arr count]; i++)
{
NSLog(#"arr %#",[Arr objectAtIndex:i]);
UIButton *Btn=[UIButton buttonWithType: UIButtonTypeCustom];
Btn.frame=CGRectMake(0, y_axis, 281, 44);
[Btn setImage:[UIImage imageNamed:#"BHCS_panel.png"] forState:UIControlStateNormal];
[Btn addTarget:self action:#selector(subCategoryBtnClicked:) forControlEvents:UIControlEventTouchUpInside];
[Btn setTag:i+100];
[view addSubview:Btn];
UILabel *nameLbl=[[UILabel alloc]initWithFrame:CGRectMake(20, y_axis,248, 44)];
nameLbl.text = [[Arr objectAtIndex:i]objectForKey:#"SubCategoryName"];
nameLbl.textColor = [UIColor whiteColor];
nameLbl.backgroundColor=[UIColor clearColor];
panelTableView.separatorColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"BHCS_panel_div1.png"]];
nameLbl.font = [UIFont fontWithName:#"Helvetica" size:12.0f];
[view addSubview:nameLbl];
y_axis=y_axis+44+1.3f;
}
}
I have tried maximum of given answers, but at the and I generally use to go for most Generalised and ideal way as follows:
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
ALLLLL of the answers here are bad and you shouldn't be looping through superviews. Classic example, with iOS 7 Apple changed the tableViewCell hierarchy and your app will now crash!
Use this instead:
How to know the UITableview row number
Updated answer
Use it like:
CGPoint hitPoint = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *hitIndex = [self.tableView indexPathForRowAtPoint:hitPoint];
Thanks to all i slove this by using below code
NSIndexPath *indexPath = [panelTableView indexPathForCell:(UITableViewCell *)sender.superview.superview];
NSLog(#"%d",indexPath.row);
None of these answers seem like a very clean solution. The way I would implement this is by using the delegate pattern. The view controller is the cell's delegate, meaning you let the cell itself handle the button press, and it tells its delegate when the button was pressed so it can handle it however it wants.
Let's say you have a tableview where each cell represents a Person object, and when the button is pressed you want to show a profile for this person. All you need to do is this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
PersonCell *cell = [tableView dequeueReusableCellWithIdentifier:#"customTableViewCell" forIndexPath:indexPath];
cell.delegate = self;
cell.person = self.people[indexPath.row];
return cell;
}
- (void)personCell:(PersonCell *)personCell didPressButtonForPerson:(Person *)person {
[self showProfileForPerson:person];
}
Then all you need to do in your button class is add a property called buttonPressedHandler that is a block passing back an instance of Person, and when you create your button and add the target do something like this:
- (void)viewDidLoad {
[super viewDidLoad];
// do whatever whatever else you need/want to here
[self.button addTarget:self selector:#selector(handleButtonPressed) forState:UIControlStateNormal];
}
- (void)handleButtonPressed {
// Make sure this is a required delegate method, or you check that your delegate responds to this selector first...
[self.delegate personCell:self didPressButtonForPerson:self.person];
}
Put this code in the button's action method:
NSIndexPath *indexPath = [yourtableviewname indexPathForCell:(UITableViewCell *)sender.superview];
NSLog(#"%d",indexPath.row);
I suggest adding a property to your custom UITableViewCell implementation class to store the indexPath. Then, when your cellForRowAtIndexPath delegate fires in your TableViewController, set the indexPath property for that cell.
customTableViewCell.h :
#interface customTableViewCell : UITableViewCell
#property NSIndexPath *indexPath;
#end
customTableViewController configure cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
customTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"customTableViewCell" forIndexPath:indexPath];
// Do whatever you want with your cell
cell.indexPath = indexPath;
return cell;
}
Then you can refer to the indexPath property in your customTableViewCell by calling self.indexPath
You can use UITableView's indexPathForCell: method like so [tableView indexPathForCell:cell];. Good Luck!