- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.userInteractionEnabled = NO;
}
I use the above code to disable a cell after a user clicks on it once. The problem I've run into is that when a cell is added to the table, that new cell is disabled, and the previously disabled one isn't anymore.
How can I fix this problem?
Cells get reused as the user scrolls the table. You need to keep track of which rows the user has disabled so in your cellForRowAtIndexPath you can set the userInteractionEnabled property (to YES or NO as needed) for every cell every time it is requested.
Update - more details.
You need to keep track of which index paths the user has selected. Add an instance variable of type NSMutableSet and add each indexPath to this in your didSelectRow... method.
Then in your cellForRow... method you need to check if the current indexPath is in the set or not. Based on the result you set the cell's userInteractionEnabled property:
cell.userInteractionEnabled = ![theSelectedPathsSet containsObject:indexPath];
where theSeletedPathsSet is your NSMutableSet instance variable.
This solution assumes the rows and sections in your table are fixed. If the user can do things that results in rows being added, removed, or moved, then you can't simply track the index paths. You need to use some other key to know which rows have been selected.
Are you using dequeueReusableCellWithIdentifier in your cellForRowAtIndexPath?
You should have something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *reuseIdentifier = #"myTableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (!cell) {
cell = [[ArticleTableViewCell alloc] init];
}
// customise cell here (like cell.title = #"Woopee";)
if (self.selectedCells containsObject:[NSString stringWithFormat:#"%d", indexPath.row]] {
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.userInteractionEnabled = NO;
}
return cell;
}
Expanding on the other answer, you can keep track of whether a specific cell has been previously selected (are therefore should be disabled) by doing something like this with the above:
Declare a property like #property (nonatomic, strong) NSMutableArray *selectedCells; then:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
[self.selectedCells addObject:[NSString stringWithFormat:#"%d", indexPath.row]];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.userInteractionEnabled = NO;
}
My laptop is about to die, but if it crashed you should look at the code the initialises the cell (alloc and init) or keep what you had before there.
You need to keep a record of which cells have been disabled. You could store the indexPath of the selected cells in an array and then use that to determine which cells should be active and not active in your cell:forRowAtIndexPath: method.
Related
I am doing using some code that I have seen work before. Essentially a user answers yes or no on a post with some buttons. Pressing yes or no updates the database, which is working correctly, and it also updates the visible UI, which is not working. This UI updates the buttons so they one is selected, other is highlighted and both are disabled for user interaction. Also it makes changes to two UILabels. The method that these buttons calls needs to update the database and retrieve the buttons from the tableViewCell and update the changes I have the methods working in another ViewController so I can not understand the difference here. Here is my cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *simpleTableIdentifier = [NSString stringWithFormat:#"%ld,%ld",(long)indexPath.section,(long)indexPath.row];
NSLog(#" simple: %#",simpleTableIdentifier);
if (indexPath.row==0) {
ProfileFirstCell *cell = [self.tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
cell = [[ProfileFirstCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:simpleTableIdentifier];
}
cell = [self createProfileCell:cell];
return cell;
}else{
YesNoCell *cell =[self.tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell==nil) {
cell=[[YesNoCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
cell = [self createYesNoCell:cell:indexPath];
return cell;
}
}
Essentially what this does is create the users profile in the first cell, and load all the questions that user asks. This is the major difference I see between the old tableView and this tableView. In createYesNoCell I create the UIElements and create tags as follows
cell.yesVoteButton.tag=indexPath.row+ yesVoteButtonTag1;
cell.noVoteButton.tag=indexPath.row+ noVoteButtonTag1;
cell.yesCountLabel.tag=indexPath.row+ yesCountLabelTag1;
cell.noCountLabel.tag=indexPath.row+ noCountLabelTag1;
The buttons have the selector that initiates a number of things. It finds which button was pressed by the following.
NSInteger index;
if(sender.tag>=yesVoteButtonTag1){
NSLog(#"Yes button pressed");
votedYes=true;
index=sender.tag-yesVoteButtonTag1;
}else{
NSLog(#"No button Pressed");
votedYes=false;
index=sender.tag-noVoteButtonTag1;
}
UILabel *yesLabel = (UILabel*) [self.tableView viewWithTag:index+yesCountLabelTag1]; // you get your label reference here
UIButton *yesButton=(UIButton *)[self.tableView viewWithTag:index+1+yesVoteButtonTag1];
NSLog(#"Tag IN METHOD: %ld",index+yesVoteButtonTag1);
UILabel *noLabel = (UILabel*) [self.tableView viewWithTag:index+1+noCountLabelTag1]; // you get your label reference here
UIButton *noButton=(UIButton *)[self.tableView viewWithTag:index+noVoteButtonTag1];
These viewWithTag calls are nil when I look at them. The only difference that I can see from my earlier implementation is that the old one had sections and one row, while this one is all rows and one section. So replacing the indexPath.section with indexPath.row should account for that. Also I checked that the tag made in cellForRowAtIndexPath is the same as the row recovered in the yes/no vote method, because it is displaced by one because of the profile cell being created at indexPath.row==0. I tried passing the cell to the yes/no vote method and tried to recover the buttons and labels with contentView as some suggestions made on similar posts. However this didn't seem to solve my problem. Really would appreciate some insight on this.
have you call the '[tableView reload]' method to update the UITableView, it may helps.
Firstly, the table reuse identifier should be used for types of cells, not one for each cell. You have two types, so you should use two fixed reuse identifiers.
ProfileFirstCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"ProfileCell"];
if (cell == nil) {
cell = [[ProfileFirstCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"ProfileCell"];
}
and
YesNoCell *cell =[self.tableView dequeueReusableCellWithIdentifier:#"YesNoCell"];
if (cell==nil) {
cell=[[YesNoCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"YesNoCell"];
}
Secondly, rather than trying to get a reference to a cell after creating the table, which isn't working for you, you should initialize the cells completely when they are created. (TableView won't create cells unless they're visible, so you shouldn't rely on their existing at all.)
createProfileCell should really be called initializeProfileCell, because you're not creating the cell in it - you already did that in the line above, or recovered an old one.
Then your call to initializeProfileCell can take a flag specifying whether it is a Yes or No cell and set its properties accordingly.
cell = [self initializeProfileCell:cell isYes:(indexPath.section==0)];
Similarly with createYesNoCell --> initializeYesNoCell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"YOURCELL_IDENTIFIER";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UILabel *title = (UILabel*) [cell viewWithTag:5];
UILabel *vensu =(UILabel*) [cell viewWithTag:7];
vensu.text = #"YOUR TEXT";
title.text = #"YOUR TEXT";
return cell;
}
I have one of the form in which the user is allowed to select some fields, For selected items i used [cell setAccessoryType:UITableViewCellAccessoryCheckmark]; ,The logic i have used is , I have taken the emptyMutableArray and at the time of didSelectRowAtIndex delegate method i checked if the selected indexPath is present in that emptyMutableArray, If its present means the item is already selected then i remove that indexPath from that emptyMutableArray, But if that indexPath is not present in it, i add the indexPath in that emptyMutableArray. Ok after that i reloads the same tableView and at that time in the method cellForRowAtIndexPath i again have one if statement for setting the cell AccessoryType where i checks if the indexPath is present in the emptyMutableArray if its there i set the AccessoryType of cell to UITableViewCellAccessoryCheckmark otherwise i set it to UITableViewCellAccessoryNone,
After that Where the problem coming is when the user will try to edit such a form then i want to show the already selected items and thats what the issue , You all will understand the problem from my code for that.
This is the cellForRowAtIndexPath delegate methods code which decides which cell should have check mark
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.selectionStyle=UITableViewCellSelectionStyleNone;
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.font=[UIFont fontWithName:#"verdana" size:13];
cell.backgroundColor=[UIColor clearColor];
else if (tableView==roleTableView) {
cell.textLabel.text = [roleAry objectAtIndex:indexPath.row];
if([emptyMutableArray containsObject:indexPath]){
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
} else {
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
}
cell.textLabel.textColor = [UIColor darkGrayColor];
return cell;
}
and this is the didSelectRowAtIndexPath method of tableView from where the user decides which item should be selected or deselected.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
else if (tableView==roleTableView){
NSLog(#"IndexPath Obj:-%#",indexPath);
if([emptyMutableArray containsObject:indexPath]){
[emptyMutableArray removeObject:indexPath];
[roleMutableAry removeObject:[roleIdAry objectAtIndex:[roleAry indexOfObject:[NSString stringWithFormat:#"%#",[roleAry objectAtIndex:indexPath.row]]]]];
}
else {
[emptyMutableArray addObject:indexPath];
[roleMutableAry addObject:[roleIdAry objectAtIndex:[roleAry indexOfObject:[NSString stringWithFormat:#"%#",[roleAry objectAtIndex:indexPath.row]]]]];
}
NSLog(#"roleMutableAry:%#",roleMutableAry);
if ([roleMutableAry count]==0) {
//[roleBtn setTitle:#"Select role" forState:UIControlStateNormal];
}
[roleTableView reloadData];
[self loadInvitees];
[peoplesOfDiffRoleTableView reloadData];
}
}
I know it was typical for me to express the exact problem, But once you understand the code i will explain where the problem occurring while showing the already selected (checked) items second time.
You cannot depend upon the IndexPath for setting some properties of cell as because the cells get reused. What I understand from your question is that you want to keep a track of the cell that has been selected and later want the table view to accordingly show the accessory type. For this you should have an array which will contain objects that will have a property as "isSelected" or anything you want. This property will help you in setting you accessory type based on its value. In your cellForRowAtIndex you need to fetch the corresponding object out from the array and check for the property "isSelected" or whatever you define and accordingly set the accessory type of the cell. I hope this will help you. Cheers!
I was having troubles using the dequeue mechanism in my tableview, i have a custom cell with a uiTextField.
When a put some value on it and do a scroll, the value of the TextField goes to another cell.
Anybody can help me on it ?
Thanks guys.
Here is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyPedidoItemCell";
PedidoItemCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
if (!cell) {
cell = [[PedidoItemCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Produto *produto = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.lblNome.text = produto.nome;
cell.lblCodigo.text = produto.codigo;
cell.lblFuncao.text = produto.funcao;
return cell;
}
When you call dequeueReusableCellWithIdentifier it may return a cell that has scrolled out of view but still contains data in the UITextField. If you don't clear the content of the UITextField or set it to the proper value for the row, you may see unwanted data.
The code you showed is using CoreData to load data for cell, but you mentioned that you don't store the data anywhere.
I suspect that the you are getting object with no data from your fetchedResultsController because you did not store any data.
So quite possibly the produto is nil and you are not setting any values on cell.
Eventually the cell you updated manually gets reused and you see the text again.
You need to first store the data that you typed into the UITextField before you can retrieve it from core data.
I have a UITableView where the user should be able to select (check) multiple rows.
I have an NSMutableArray in my controller to hold the selected items, and in my cellForRowAtIndexPath method, I check whether the item is in that array and return the cell in a checked/unchecked state accordingly.
Here's the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = kContactCellReuseIdentifier;
static NSString *searchIdentifier = kContactSearchCellReuseIdentifier;
POContactCell *cell;
// Configure the cell...
if (tableView == self.tableView) {
cell = (POContactCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.contact = self.contacts[indexPath.row];
NSLog(#"Returned cell with name %#", cell.contact.name);
} else {
cell = (POContactCell*)[tableView dequeueReusableCellWithIdentifier:searchIdentifier forIndexPath:indexPath];
cell.contact = self.searchResults[indexPath.row];
}
if ([self.selectedContacts containsObject:cell.contact])
{
NSLog(#"was checked");
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
cell.accessoryType = UITableViewCellAccessoryNone;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
POContactCell* tappedCell = (POContactCell*)[self tableView:tableView cellForRowAtIndexPath:indexPath];
NSLog(#"Selected contact %#", tappedCell.contact.name);
if ([self.selectedContacts containsObject:tappedCell.contact]) {
// cell is already selected, so deselect it
NSLog(#"It's already selected, so deselect it");
[self.selectedContacts removeObject:tappedCell.contact];
tappedCell.accessoryType = UITableViewCellAccessoryNone;
}
else
{
NSLog(#"It's not already selected, so select it");
[self.selectedContacts addObject:tappedCell.contact];
tappedCell.accessoryType = UITableViewCellAccessoryCheckmark;
}
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:NO];
}
This code works... except for the first selection. The first cell that the user taps will get checked and will never get unchecked. I see from the log statements that all the cells are going through the exact same process and it's correctly recognizing the selection state of the first tapped row too, even though the accessory view doesn't reflect it.
After the first selection, all the other rows work perfectly.
Any debugging ideas?
You should be putting self.contacts[indexPath.row] (or self.searchResults[indexPath.row], as appropriate) in your array of selected items, and checking whether or not those objects exist or not in the array when the user taps a cell. You are almost doing that, it would appear, by setting cell.contact to the object from your data source and checking for cell.contact in your array. But I'd try putting the object directly into your array, e.g.
id contact = self.contacts[indexPath.row];
if ([self.selectedContacs containsObject:contact])
...
and stop checking if cell.contact is in the array to determine "selected-ness".
In a UITableView there is a small set of actual UITableViewCell objects in memory, and they get re-used. The root of your problem could very well be this, because you are checking to see if cell.contact is in your set of selected items; when a cell is reused, unless you wrote your own prepareForReuse, the previous value of your custom attributes may not (likely will not) be cleared.
Does that make sense?
Before I post the question itself, I need to state this is a jailbreak app. This is why I'm writing in "bizarre" folders in the filesystem.
Let's continue.
Here is my cellForRowAtIndexPath method:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"pluginCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
if(indexPath.row == 0)
{
cell.textLabel.text = #"default";
}else
{
//Get the plugin's display name.
NSBundle *currentPlugin = [[NSBundle alloc] initWithPath:[NSString stringWithFormat:#"/Library/Cydeswitch/plugins/%#", [plugins objectAtIndex:indexPath.row - 1], nil]];
cell.textLabel.text = [[currentPlugin localizedInfoDictionary] objectForKey:#"CFBundleDisplayName"];
if(cell.textLabel.text == nil)
{
//No localized bundle, so let's get the global display name...
cell.textLabel.text = [[currentPlugin infoDictionary] objectForKey:#"CFBundleDisplayName"];
}
[currentPlugin release];
}
if([[[cell textLabel] text] isEqualToString:[settings objectForKey:#"pluginToExecute"]])
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
currentCell = [cell retain];
}
return cell;
}
Like you can see, this method uses a member called currentCell to point to the cell that is currently "selected". This is an options table and the user should be able to have only one cell with the Checkmark accessory icon at any time.
When the use selects another cell, he is changing an option and the Checkmark is supposed to disappear from the current cell and appear in the newly appeared cell. I do that like this:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
currentCell.accessoryType = UITableViewCellAccessoryNone;
[currentCell release];
currentCell = [[self tableView:tableView cellForRowAtIndexPath:indexPath] retain];
NSLog(#"CURRENT CELL %#", currentCell.textLabel.text);
currentCell.accessoryType = UITableViewCellAccessoryCheckmark;
}
But it doesn't work. The moment I tap another cell, the Checkmark correctly disappears from the old cell, but it never shows up in the new cell.
I know the selection work fine because that NSLog there prints the new cell's text just fine.
I have tried keeping track of the indexPath before, but it didn't work at all. When I tried using indexPaths instead of pointers to cells, when the user tapped the cell nothing happened at all (at least with my current approach the checkmark disappears from the old cell).
I think it has something to do with cellForRowAtIndexPath because if I keep pointing at the cells the checkmark disappears, but for some reason when trying to change the accessory type from a cell fetched with cellForRowAtIndexPath it doesn't seem to work at all.
Any help will be appreciated.
Typo? Try this:
currentCell = [[self.tableView cellForRowAtIndexPath:indexPath] retain];
You mustn't keep track of the last selected cell the way you are. Cell's get reused. Use an ivar to keep track of the indexPath or some other key appropriate to your data.
Then in the didSelect... method you get a reference to the old cell using the saved indexPath or key. In the cellForRow... method you need to set the proper accessoryType based on whether the current indexPath matches your saved indexPath.
Lastly, do not call your own delegate/data source method. When getting a reference to a cell, ask the table view for it directly.
BTW - you are over-retaining currentCell in your cellForRow... method. There is no need to retain it all in that method unless it is the first time you are making the assignment.