I'm using a UITableView to allow selection of one (of many) items. Similar to the UI when selecting a ringtone, I want the selected item to be checked, and the others not. I would like to have the cell selected when touched, then animated back to the normal color (again, like the ringtone selection UI).
A UIViewController subclass is my table's delegate and datasource (not UITableViewController, because I also have a toolbar in there).
I'm setting the accessoryType of the cells in cellForRowAtIndexPath:, and updating my model when cells are selected in didSelectRowAtIndexPath:. The only way I can think of to set the selected cell to checkmark (and clear the previous one) is to call [tableView reloadData] in didSelectRowAtIndexPath:. However, when I do this, the animating of the cell deselection is weird (a white box appears where the cell's label should be). If I don't call reloadData, of course, the accessoryType won't change, so the checkmarks won't appear.
I suppose I could turn the animation off, but that seems lame. I also toyed with getting and altering the cells in didSelectRowAtIndexPath:, but that's a big pain.
Any ideas? Abbreviated code follows...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* aCell = [tableView dequeueReusableCellWithIdentifier:kImageCell];
if( aCell == nil ) {
aCell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:kImageCell];
}
aCell.text = [imageNames objectAtIndex:[indexPath row]];
if( [indexPath row] == selectedImage ) {
aCell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
aCell.accessoryType = UITableViewCellAccessoryNone;
}
return aCell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
selectedImage = [indexPath row]
[tableView reloadData];
}
I solved it like hatfinch suggested in his edit. My code sample make sure there's only one row checked but I'm sure you can tweak it if thats not what you need.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int newRow = [indexPath row];
int oldRow = [lastIndexPath row];
if (newRow != oldRow)
{
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:
indexPath];
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:
lastIndexPath];
oldCell.accessoryType = UITableViewCellAccessoryNone;
lastIndexPath = indexPath;
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
I had this exact same problem and never solved it.
In the end it was moot for me because I had to listen for changes in the model (the selection could change outwith the user's control if a connection was lost) so I just changed the model and let the notification reload the data, but it didn't look too pretty (it was like a non-animated deselection, but one run loop iteration late!)
I suspect the only way to do it is, as you suggest, keeping a cache of references to the cells themselves. This shouldn't be too messy if you write a UITableViewCell subclass to encapsulate the logic, overriding -prepareForReuse.
Still, not pretty, and if the ringtone guys at Apple had to do it this way, they ought to have pressed the UITableView team harder to fix it!
EDIT:
Well, what do you know?! This is really easy after all. -[UITableView cellForRowAtIndexPath] will give you the actual cells (or nil if they're offscreen) and you can just change the accessoryType.
I have a cleaner way of doing it.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (cell.accessoryType == UITableViewCellAccessoryNone) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
I'm using this simple and nice working mechanism with reloadRowsAtIndexPaths.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSArray *rowsArray = [NSArray arrayWithObjects:indexPath, selectedIndexPath, nil];
self.selectedIndexPath = indexPath;
[tableView reloadRowsAtIndexPaths:rowsArray withRowAnimation:UITableViewRowAnimationNone];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
.... skipped usual code .....
if ([selectedIndexPath compare:indexPath] == NSOrderedSame) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
This will work as desired...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int newRow = [indexPath row];
int oldRow = [lastIndexPath row];
if (newRow == oldRow){
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (cell.accessoryType == UITableViewCellAccessoryNone) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
lastIndexPath = indexPath;
}
if (newRow != oldRow)
{
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:lastIndexPath];
oldCell.accessoryType = UITableViewCellAccessoryNone;
lastIndexPath = indexPath;
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:index];
oldCell.accessoryType = UITableViewCellAccessoryNone;
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
index = indexPath;
}
Related
I have Table View with Multiple sections in it.
I want to allow only one row selected...My tableview's Selection Property is Set to:SingleSelectioin
Now I am doing this to set the checkmark for selected row
if([tableView cellForRowAtIndexPath:indexPath].accessoryType == UITableViewCellAccessoryCheckmark)
{
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
}
else
{
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
}
Now what I want is that when I select any other cell, the previously selected cell should be set back to unselected.
I have tried with this
lastIndexpath=indexPath;
and then
[tableView deselectRowAtIndexPath:lastIndexpath animated:YES];
but it gives me bad access on lasIndexPath
EXC_BAD_ACCESS (code=1,address= 0xfe000008)
Kindly Help me with this
any New Suggestions are also welcomed
You want to mark your selected cell, then in didSelectRowAtIndexPath you can write code like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = (UITableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
cell = (UITableViewCell*)[tableView cellForRowAtIndexPath:previousSelectedIndexPath];
[cell setAccessoryType: UITableViewCellAccessoryNone];
if(previousSelectedIndexPath!=nil)
{
[previousSelectedIndexPath release];
previousSelectedIndexPath = nil;
}
previousSelectedIndexPath = [indexPath retain];
}
where previousSelectedIndexPath is previously selected index;
Declare NSIndexPath *lastIndexpath in .h of your controller:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if( lastIndexpath){
if([tableView cellForRowAtIndexPath:lastIndexpath].accessoryType == UITableViewCellAccessoryCheckmark)
{
[tableView cellForRowAtIndexPath:lastIndexpath].accessoryType = UITableViewCellAccessoryNone;
}
else
{
[tableView cellForRowAtIndexPath:lastIndexpath].accessoryType = UITableViewCellAccessoryCheckmark;
}}
lastIndexpath=[indexPath retain]
if([tableView cellForRowAtIndexPath:indexPath].accessoryType == UITableViewCellAccessoryCheckmark)
{
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
}
else
{
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
}
}
//Take a variable of type indexpath in .h
NSIndexPath *myIndexPath
//Now check it in your cellForRowAtIndexPath: delegate method ..
if(indexPath == myIndexPath)
cell.accessoryType = UITableViewCellAccessoryCheckmark;
else
cell.accessoryType = UITableViewCellAccessoryNone;
//and in your didSelect: Delegate method ...
[tableView cellForRowAtIndexPath:indexPath].accessoryType == UITableViewCellAccessoryCheckmark
myIndexPath = indexPath;
[tableView reloadData];
Hope this will help you.
in delegate method willSelectRowAtIndexPath , get the current selected index path, set accerroryType to none.
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath *currentSelectedIndexPath = [tableView indexPathForSelectedRow];
[tableView cellForRowAtIndexPath:currentSelectedIndexPath].accessoryType = UITableViewCellAccessoryNone;
if ([indexPath isEqualTo: currentSelectedIndexPath])
{
return nil;
}
return indexPath;
}
I want to make a typical situation: when user selects any cell, it's accessoryType turns in checkmark. Only one cell's accessoryType can be checkmark. And then I wanna save in NSUserDefaults indexPath.row so my app will be able to know which cell user selected and make some changes in options. So I wrote this wrong code:
didSelectRowAtIndexPath
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// checkedIndexPath is NSIndexPath
if(self.checkedIndexPath)
{
UITableViewCell* uncheckCell = [tableView
cellForRowAtIndexPath:self.checkedIndexPath];
uncheckCell.accessoryType = UITableViewCellAccessoryNone;
}
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
self.checkedIndexPath = indexPath;
[[NSUserDefaults standardUserDefaults]setObject:[NSNumber numberWithInt:self.checkedIndexPath.row]forKey:#"indexpathrow" ];
}
cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Part of code from cellForRowAtIndexPath
if(indexPath.row == [[[NSUserDefaults standardUserDefaults]objectForKey:#"indexpathrow"]intValue ])
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
However, this code works badly. When you open the UITableView, there is an already selected cell in the table and when you press another there are two checkmarked cells...How can I improve my code or should I change it whole ? Any suggestions ?
Thanks !
Try this code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// checkedIndexPath is NSIndexPath
NSIndexPath *previousSelection = self.checkedIndexPath;
NSArray *array = nil;
if (nil != previousSelection) {
array = [NSArray arrayWithObjects:previousSelection, indexPath, nil];
} else {
array = [NSArray arrayWithObject:indexPath];
}
self.checkedIndexPath = indexPath;
[tableView reloadRowsAtIndexPaths:array withRowAnimation: UITableViewRowAnimationNone];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Part of code from cellForRowAtIndexPath
NSString *cellID = #"CellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (nil == cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
[cell autorelease];
}
// some code for initializing cell content
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if(self.checkedIndexPath != nil && indexPath.row == self.checkedIndexPath.row)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
I have a selection that will select just 1 row of category.
but I will like it to select Spirits row when loaded.
Something like this:
At the moment it comes to this without selecting anything:
Where shall I do the compare for the in order for it to selectRowAtIndexPath?
// Customize the appearance of table view cells.
- (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:CellIdentifier] autorelease];
}
categoryString = [arrayCategory objectAtIndex:indexPath.row];
cell.textLabel.text = categoryString;
if (cell.textLabel.text == categoryString) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* newCell = [tableView cellForRowAtIndexPath:indexPath];
int newRow = [indexPath row];
int oldRow = (lastIndexPath != nil) ? [lastIndexPath row] : -1;
if(newRow != oldRow)
{
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
newCell.highlighted =YES;
UITableViewCell* oldCell = [tableView cellForRowAtIndexPath:lastIndexPath];
oldCell.accessoryType = UITableViewCellAccessoryNone;
lastIndexPath = indexPath;
oldCell.highlighted = NO;
}
[tableView deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:YES];
}
Use selectRowAtIndexPath:animated:scrollPosition: method to select a row programatically.
Few things to correct in your code. You should use isEqualToString: method to match the strings, like this, if ([cell.textLabel.text isEqualToString:categoryString]) {. Next thing is, you are assigning the categoryString to cell.textLabel.text, and on the next line you are matching them, so it would always return YES. I think you are matching the wrong values.
You have to manually set multiple check marks. for that you can use UIImageview for each UITableViewCell and as per the stored old data, you have to set check marks in didSelectRowAtIndexPath(delegate method of UITableView).
This
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (cell.accessoryType == UITableViewCellAccessoryNone) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
... modifies the "accessoryType" of every 6th cell INSTEAD of just the selected row. What am I missing?
Thanks
UPDATED: Here is the cell creation code ...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *TC = #"TC";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: TC];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:PlayerTableViewCell] autorelease];
}
NSUInteger row = [indexPath row];
cell.textLabel.text = [NSString stringWithFormat:#"Person %d", row+1];
return cell;
}
MY SOLUTION based on the marked answer below is ...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// EVERY row gets its one Identifier
NSString *TC = [NSString stringWithFormat: #"TC%d", indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: TC];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TC] autorelease];
}
NSUInteger row = [indexPath row];
cell.textLabel.text = [NSString stringWithFormat:#"Person %d", row+1];
return cell;
}
If there is a better way I'm all ears. Would be nice if we could just change the SPECIFIC Cell according to the NSIndexPath passed in someday (at least that seems a whole lot more intuitive to me).
I got it to work by using setting the accessory type in cellForRowAtIndexPath based on variable which keeps track of the selected row. Then in didSelectRowAtIndexPath i just deselect the row, update the variable, and reload the table.
CODE SAMPLE:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.text = [NSString stringWithFormat:#"%d", indexPath.row];
if ([_cells indexOfObjectIdenticalTo:_selectedCell] == indexPath.row) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
_selectedCell = [_cells objectAtIndex:indexPath.row];
[tableView reloadData];
}
Yeah, just figured out. These are the same cells ( logs below are NSLog( #"%d %#", row, cell ) ).
2010-04-15 12:43:40.722 ...[37343:207] 0 <MyListCell: 0x124bee0; baseClass = UITableViewCell; frame = (0 0; 320 44); autoresize = RM+TM; layer = <CALayer: 0x124c3a0>>
2010-04-15 12:43:47.827 ...[37343:207] 10 <MyListCell: 0x124bee0; baseClass = UITableViewCell; frame = (0 31; 340 44); autoresize = W; layer = <CALayer: 0x124c3a0>>
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: PlayerTableViewCell];
dequeueReusableCellWithIdentifier uses already created cell, if the previous one is out of a screen.
So, you should avoid using "dequeueReusableCellWithIdentifier" - create a dictionary NSIndexPath -> UITableViewCell and use it instead of dequeueReusableCellWithIdentifier.
ADDED:
You can also check if the (rowNumber+1) exists in a winningNumbers array and replace accessory in a tableView:cellForRowAtIndexPath: method. This should work too
I'm trying to add checkmarks to items when the user select rows in a table view. However, the view is not refreshed and the checkmarks do no show up:
- (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* oldCell = [self tableView:tv cellForRowAtIndexPath:[NSIndexPath indexPathForRow:selectedIndex inSection:0]];
oldCell.accessoryType = UITableViewCellAccessoryNone;
if (indexPath.section == 0) {
selectedIndex = indexPath.row;
}
UITableViewCell* newCell = [self tableView:tv cellForRowAtIndexPath:indexPath];
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
[tv deselectRowAtIndexPath:indexPath animated:NO];
}
What could be a reason for that?
Call cellForRowAtIndexPath directly on the tv object instead of through self to make sure you get a proper cell reference back:
- (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *oldCell = [tv cellForRowAtIndexPath:[NSIndexPath indexPathForRow:selectedIndex inSection:0]];
oldCell.accessoryType = UITableViewCellAccessoryNone;
if (indexPath.section == 0) {
selectedIndex = indexPath.row;
}
UITableViewCell *newCell = [tv cellForRowAtIndexPath:indexPath];
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
[tv deselectRowAtIndexPath:indexPath animated:NO];
}
Also make sure that in cellForRowAtIndexPath, you have this logic:
...
if (indexPath.row == selectedIndex)
cell.accessoryType = UITableViewCellAccessoryCheckmark;
else
cell.accessoryType = UITableViewCellAccessoryNone;
...
return cell;
otherwise, a checkmark will remain on a cell after it scrolls off the screen and comes back on screen even after you've selected another cell.