I'm new to iOS and I'm having issues with adding/removing a cell accessoryTypeCheckmark when a user selects a cell. I have a friends list in a UITableView and the user should be able to select multiple friends and have a checkmark appear next to them. If the user taps on a friend that has already been selected, the checkmark should disappear. I am keeping track of the friends that have been selected in a NSMutableArray titled 'friends_selected'.
I have the cellForRowAtIndex path set up to check if the friends_selected array contains the username, and if so add a checkmark.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
FriendsListCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.usernameID.text = [self.friends_username objectAtIndex:indexPath.row];
if ([self.friends_selected containsObject:cell.usernameID.text]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
In my didSelectRowAtIndexPath I am adding or removing the username from the friends_selected array and reloading the table data.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
FriendsListCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.usernameID.text = [self.friends_username objectAtIndex:indexPath.row];
if ([self.friends_selected containsObject:cell.usernameID.text]) {
[self.friends_selected removeObject:cell.usernameID.text];
}
else {
[self.friends_selected addObject:cell.usernameID.text];
}
[tableView reloadData];
}
Right now the friends_selected array is correctly updating with which usernames have been selected or deselected. There are no checkmarks appearing, however the cell will become highlighted when selected and will remain highlighted even if unselected. Any help would be great, I've been stuck all day on this issue that seems like a simple solution.
In your didSelectRow... method you should replace:
FriendsListCell *cell = (FriendsListCell *)[tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
with:
FriendsListCell *cell = (FriendsListCell *)[tableView cellForRowAtIndexPath:indexPath];
You don't want a new cell, you want the actual cell for the selected row.
And do not call reloadData too. Either update the cell directly or just call reloadData, not both.
Related
I'm trying to make the selected cell (of a custom UITableViewCell) to have a checkmark on it. I tried the following code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
CategorieCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"cellID" forIndexPath:indexPath];
if (customCell.accessoryType == UITableViewCellAccessoryDisclosureIndicator) {
customCell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
customCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
}
But when I selected a cell, nothing happened.
Your mistake is that you dequeue a new custom cell. Instead, you have to find out the cell that actually is selected. Change the first line to:
CategorieCell *customCell = (CategorieCell *)[tableView cellForRowAtIndexPath:indexPath];
This should be it.
This is my code, which shows a problem for 14 rows of a table view. For one screen, 6 cells are visible.
When I tapped the 2nd cell, the 10 cell also has a checkmark, tap 3rd then 11th checkmarked, 1st then 9th checkmarked, tap 1st, then 8th also checkmarked...but the grey highlight does not behave like this, only one cell can be highlighted.
If I tap a cell after one is tapped on the the same screen, the one just checkmarked will be cleared for checkmark, which makes sense. However, if after I tapped one cell and then scroll the tableview lower, I can tap a cell and it shows checkmark as well, i.e. the one just checked on top screen still has a checkmark. So if I scroll up and down, and tap one cell each time after I scroll to the other side, I can put a checkmark for every cell, all checkmarks are shown.
It is really weird, and I have tried a lot of ways to solve it, but seems I have some basic understanding of UITableView missing, can anyone figure it out, please? Thanks.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID= #"UITableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
cell.imageView.image = [UIImage imageNamed:#"earth.png"];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =[tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =[tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryNone;
}
You're not storing the existence/absence of your checkmark in any sort of persisted data source and showing/hiding it in cellForRowAtIndexPath. Because of this, your table is reusing your cells, and it just throws it up as it found it. So if it reuses a cell that had your checkmark accessory enabled, it shows it as enabled (because you didn't specifically disable it in cellForRow)
I would keep an NSMutableArray of selected indexPath objects. Add the indexPath in didSelectRowAtIndexPath, and remove it in didDeselectRowAtIndexPath. Then, in cellForRowAtIndexPath, check if the current indexPath exists in the array and enable the checkmark if so, disable if not.
Stonz2's answer is right.
For example, you should create an object for table view data source and it should has a property to show is it checkmarked.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
...
Item *item = [self.itemArray objectAtIndex:indexPath.row];
if (item.checkmarked) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:NO];
Item *tappedItem = [self.itemArray objectAtIndex:indexPath.row];
tappedItem.checkmarked = !tappedItem.checkmarked;
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
I have a problem reloading selected cells.
Trying to follow a tutorial but now stuck with a strange problem.
I have made a list where selecting an item is suppose to show/remove a checkmark.
However, the cell is not updated until I select another cell in the list.
Any ideas?
I have attached the relevant sourcecode below.
Thanks, K
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ListPrototypeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
XYZToDoItem *toDoItem = [self.toDoItems objectAtIndex:indexPath.row];
cell.textLabel.text = toDoItem.itemName;
if(toDoItem.completed) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:NO];
XYZToDoItem *toDoItem = [self.toDoItems objectAtIndex:indexPath.row];
toDoItem.completed = !toDoItem.completed;
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
You've accidentally implemented didDeselectRowAtIndexPath instead of didSelectRowAtIndexPath.
Your problem is tha tha you didn't use the didSelectRowAtIndexPath but the didDeselectRowAtIndexPath so right now every time you deselect your cell you updateyour cell :)
I have a text array backing a TableView in iOS. In the cellForRowAtIndexPath: method I return a UITableViewCell* which is populated with text from the backing array. indexPath is used as the index into the backing array.
I now want to add a "Done" button to the last cell in the TableView. In my StoryBoard I've created a second (prototype) TableView Cell and gave it the identifier "ButtonCell". I've also added an extra element to the end of the backing array so numberOfRowsInSection: can return the count of the backing array and everything will just work.
I thought I would set the text of the last array element to something like #"donebutton" and then I could check for that in cellForRowAtIndexPath:. If it comes up true, I would know I'm at the end of my array and to return the "ButtonCell" cell instead of the normal "Cell". Thing is, it's not quite working right. What's the best way to accomplish this? Code snip is below.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
static NSString *ButtonCellIdentifier = #"ButtonCell";
UITableViewCell *bcell = [tableView dequeueReusableCellWithIdentifier:ButtonCellIdentifier forIndexPath:indexPath];
NSString *rowtext = [_mArCellData objectAtIndex:indexPath.row];
// return button cell if last item in list
if ([rowtext isEqualToString:[NSString stringWithFormat:#"%d", SUBMIT_BUTTON]])
{
NSLog(#"hit last row, so using button row");
return bcell;
}
cell.textLabel.text = rowtext;
return cell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
static NSString *ButtonCellIdentifier = #"ButtonCell";
UITableViewCell *cell;
if (indexPath.row != ([_mArCellData count] - 1) { // if not the last row
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// configure cell...
} else { // last row
cell = [tableView dequeueReusableCellWithIdentifier:ButtonCell];
// configure button cell...
}
return cell;
}
I would just change your if statement to:
if ([tableView numberOfRowsInSection:0] == indexPath.row + 1) {
NSLog(#"hit last row, so using button row");
bcell.textLabel.text = rowtext;
return bcell;
}
This is a little more abstracted than your solution and doesn't rely on a property of a cell being set to anything in particular. I like the way #bilobatum put the dequeueReusableCellWithIdentifier: call in the if statement. That should save some memory as well.
EDIT: I also noticed that you are setting cell text, but not bcell text.
When adding a checkmark to selected table cells, im seeing check appear in other cells also.
my didSelectRowAtIndexPathCode is:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
PFObject *player = [squadListArray objectAtIndex:indexPath.row];
NSString *playerName = [player valueForKey:#"fullName"];
NSLog(#"%#", playerName);
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
selectedCell.accessoryType = UITableViewCellAccessoryCheckmark;
}
The NSLog has expect results, only showing the one selection.
Any ideas? Do you need me to show any other code?
Thanks
In your cellForRowAtIndexPath you can't be configuring the cell properly when the cell gets reused. You should always be setting (and resetting) all of the properties of the cell from your data model.
You must have a data model that is being used to tell the table view how many rows it has and what each cell should look like. During didSelectRowAtIndexPath you should be updating your data model with the selected information. Then, in cellForRowAtIndexPath, you can use the information in the data model to decide if the cell has a checkmark or not. If it does you add it, if it doesn't you explicitly remove it (to prevent it being left there if the cell was reused).
Your cell is being recycled by other rows. In the method, cellforrowatindexpath, add the following line at the end:
selectedCell.accessoryType = UITableViewCellAccessoryNone;
Cells are cached and re-used. You need to only save the fact you were selected (maybe in PFObject) and then set the accessory each time you configure a cell.
You need to explicitly tell that you don't want other cells to have the checkmark.
if ([self shouldSelectCell]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
You could try doing the following:
Create NSMutableSet that holds the indexes of selected cells.
#property(strong, nonatomic) NSMutableSet *selectedCells;
-(NSMutableSet *)selectedCells{
if(_selectedCells){
return _selectedCells;
}
_selectedCells = [[NSMutableSet alloc]init];
return _selectedCells;
}
On didSelect update the set and select the cell:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[self.selectedCells addObject:indexPath];
[tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionMiddle];
}
Remove the indexPath on didDEselect
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryNone;
[self.selectedCells removeObject:indexPath];
}
Inside the
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
update the cell as:
if([self.selectedCells containsObject:indexPath]){
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}else{
cell.accessoryType = UITableViewCellAccessoryNone;
}