Im using UITableView with checkboxes. In my cellForRowAtIndexPath method, I have set selected few cells as follows,
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(tableView == self.sideBarTbl){
cell.textLabel.text = [titleArray objectAtIndex:indexPath.row];
cell.tag = indexPath.row;
if(indexPath.row == 0){
[cell setSelected:true];
}else if(indexPath.row == 1){
[cell setSelected:true];
}else {
[cell setSelected:false];
}
}
In short, if the row is 0,1 then Im checking the checkboxes of the UITableView.
when the user clicks the row 0 or row 1, it should call didDeselectRowAtIndexPath since row 0 and row 1 checkmarks are already checked. But, it is always calling the didSelectRowAtIndexPath. no matter if the checkbox is checked or not, it always calls the didSelectRowAtIndexPath. How can I let it call didDeselectRowAtIndexPath when the checkbox is checked and call didSelectRowAtIndexPath when the checkbox is not checked?
didDeselectRowAtIndexPath method will only be called for cells which has been touched to select before. It's not related to the cell's selected property. If you want to do something for deselected cells, just record the selection manually in NSMutableArray with didSelectRowAtIndexPath method. Forget the didDeselectRowAtIndexPath. For reference: UITableView cell on double click should go to previous state
And an Objective-C example:
#interface SomeViewController : UIViewController<UITableViewDelegate> {
NSMutableArray *selectedIndexPaths;
}
#implementation SomeViewController
//......
- (void)viewDidLoad {
[super viewDidLoad];
selectedIndexPaths = [NSMutableArray array];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if ([selectedIndexPaths containsObject:indexPath]) {
[selectedIndexPaths removeObject:indexPath];
//Do something when deselecting.
}
else{
[selectedIndexPaths addObject:indexPath];
//Do something when selecting.
}
}
//......
#end
If the selection is single and exclusive, like a radiobox, just use single variable NSIndexPath *selectedIndexPath; to record selection in didSelectRowAtIndexPath, after handling the deselecting process for previous selected cell.
Use the following code in your cellForRowAtIndexPath instead of [cell setSelected:true];
[tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];`
Use the following code when the user wants to deselect. Use it in your didDeselectRowAtIndexPath
[tableView deselectRowAtIndexPath:indexPath animated:YES];
Finally, you can get the selected indexes as follows,
-(void) getSelectedIndices
{
NSArray *indexPathArray = [tableView indexPathsForSelectedRows];
for(NSIndexPath *index in indexPathArray)
{
NSLog(#"Index: %ld",(long)index.row);
}
}
Revert back in case if you have any queries
Related
I just saw a post about selecting multiple UITableView rows and based on which row/rows are selected, something will happen when you press a button linked to an IBAction.
It's an interesting question and I've played around with it, but now I'm stuck a bit.
As you can see I have a NSMutableArray called selectedIndexes, and the selected row/rows are shown in the console using a NSLog. This works fine:
- (IBAction)doSomethingWithSelectedRows:(id)sender {
for (NSNumber *data in self.selectedIndexes) {
NSLog(#"Row %ld is selected.", (long)[data integerValue] +1);
}
if (WHAT SHOULD BE PUT HERE?) {
}
//Do whatever you want here.
}
The question is, what should be put in that if statement above if you want this to happen:
if (row 2 and row 1 are selected {
//Run this code.
}
UPDATE:
Here's more code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
if ([selectedCell accessoryType] == UITableViewCellAccessoryNone) {
[selectedCell setAccessoryType:UITableViewCellAccessoryCheckmark];
[_selectedIndexes addObject:[NSNumber numberWithInteger:indexPath.row]];
} else {
[selectedCell setAccessoryType:UITableViewCellAccessoryNone];
[_selectedIndexes removeObject:[NSNumber numberWithInteger:indexPath.row]];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (IBAction)doSomethingWithRowsSelected:(id)sender {
for (NSNumber *data in self.selectedIndexes) {
NSLog(#"Row %ld is selected.", (long)[data integerValue] +1);
}
}
While you can use an array, as you do, an NSMutableIndexSet is a much better data structure for this task. It allows you to check whether a given index is in the set without having to iterate through all of the objects.
#property (strong,nonatomic) NSMutableIndexSet *selectedRows;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = indexPath.row;
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
if ([self.selectedRows containsIndex:row]) {
[self.selectedRows removeIndex:row];
[selectedCell setAccessoryType:UITableViewCellAccessoryNone];
} else {
[self.selectedRows addIndex:indexPath.row];
[selectedCell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Now you can easily check if any two rows are selected:
- (IBAction)doSomethingWithRowsSelected:(id)sender {
if ([self.selectedRows containsIndex:1] && [self.selectedRows containsIndex:2]) {
// Do whatever
}
}
Also, don't forget to set the accessory type in cellForRowAtIndexPath based on [self.selectedRows containsIndex:indexPath.row] otherwise your checkmarks will be wrong when you scroll.
Here is what I will do,
1st, get the array of selected rows by using indexPathsForSelectedRows,
2nd, iterate the array to check if if indexPath.row is 1 or 2.
3rd, if your if statement's condition is true, then do ...
I have a tableview which can be expanded on selecting the cell and collapses on selecting again. When you select, the cell should expand and display a label and when you select again it collapses and hides the label . The expanding and collapsing works fine, but if i scroll the tableview after expanding a cell it behaves weird. Once it goes out of the view and comes back , the cell will have the expanded cell's height but the label which is supposed to be shown in expanded cell is hidden.If i select the cell again it collapses and displays the label. I use ,
- (CGFloat)tableView:(UITableView *)t heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:t cellForRowAtIndexPath:indexPath];
if([self cellIsSelected:indexPath])
return cell.frame.size.height+35;
return cell.frame.size.height;
}
- (BOOL)cellIsSelected:(NSIndexPath *)indexPath {
// Return whether the cell at the specified index path is selected or not
NSNumber *selectedIndex = [self.selectedIndexes objectForKey:indexPath];
return selectedIndex == nil ? FALSE : [selectedIndex boolValue];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Deselect cell
NSLog(#"Select cell:%#",indexPath);
[self.tableView deselectRowAtIndexPath:indexPath animated:TRUE];
if([self pickTaskForIndexPath:indexPath].productSpecialMessage){
BOOL isSelected = ![self cellIsSelected:indexPath];
NSNumber *selectedIndex = [NSNumber numberWithBool:isSelected];
[self.selectedIndexes setObject:selectedIndex forKey:indexPath];
PickTaskTableviewCell *cell= [self.tableView cellForRowAtIndexPath:indexPath];
cell.message.hidden=false;
cell.messageLabel.text=[self pickTaskForIndexPath:indexPath].productSpecialMessage;
cell.messageLabel.lineBreakMode=NSLineBreakByTruncatingTail;
cell.messageLabel.numberOfLines=3;
if(cell.messageLabel.hidden==true){
cell.messageLabel.hidden = false;
} else {
cell.messageLabel.hidden = true;
}
NSLog(#"message:%#",cell.messageLabel.text);
[cell layoutIfNeeded];
}
self.tableView.rowHeight=UITableViewAutomaticDimension;
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
indexPath is added to the selectedIndexes on didSelectRowAtIndexPath
Please help me
Cells should be configured only within cellForRowAtIndexPath. When a state change occurs that makes a cell need to look different, just reload that cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
PickTaskTableviewCell *cell = (PickTaskTableviewCell *)[tableView dequeueReusableCellWithIdentifier:#"cell"];
// everything else you do to configure the cell goes here, then ...
// check the logic here, we want one condition that tells us whether to show the labels
if([[self cellIsSelected:indexPath] && self pickTaskForIndexPath:indexPath].productSpecialMessage){
// don't need these here
//NSNumber *selectedIndex = [NSNumber numberWithBool:isSelected];
// [self.selectedIndexes setObject:selectedIndex forKey:indexPath];
// PickTaskTableviewCell *cell= [self.tableView cellForRowAtIndexPath:indexPath];
cell.message.hidden=false;
cell.messageLabel.text=[self pickTaskForIndexPath:indexPath].productSpecialMessage;
cell.messageLabel.lineBreakMode=NSLineBreakByTruncatingTail;
cell.messageLabel.numberOfLines=3;
cell.messageLabel.hidden=NO;
} else {
cell.message.hidden=YES;
cell.messageLabel.hidden=YES;
}
NSLog(#"message:%#",cell.messageLabel.text);
// don't need this here
// [cell layoutIfNeeded];
return cell;
}
Selection (and presumably deselection) cause the need to update the cell, so...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// don't deselect it here, just reload it
// more on this later...
[self.selectedIndexes setObject:selectedIndex forKey:indexPath];
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
// probably do the same in didDeselectRowAtIndexPath:
One last (optional) point. There's no need to maintain your own list of selected index paths, UITableView does that for you, so you could delete your selectedIndexes property and just use the table view methods, e.g....
- (BOOL)cellIsSelected:(NSIndexPath *)indexPath {
// Return whether the cell at the specified index path is selected or not
return [[self.tableView indexPathsForSelectedRows] containsObject:indexPath];
}
Hey I'm working on a screen where user have option groups for example "Drink" which is title of section in my tableView, and their choices are "7up", "coke", etc which are cells of my table.
Now every Option Group choice (every cell in order words) has one radio button. I want to implement this. I'm facing problem if user selects any cell's radio button then other radio buttons should be deselected but how?
any help please
You should create a function to check your radio button from your custom cell and implements a delegate method to inform your TableViewController that your button on that cell was selected.
Your TableViewController needs to implements that delegate (dont forget to set each cell.delegate = self).
Then in your delegate method you create a loop to uncheck all of the radio buttons of the cells in the section except the cell you just checked.
Something like that :
This is a custom UITableViewCell with a button.
The images checked and uncheck need to look like a radio button checked and uncheked
Here is the .h file :
//RadioCell.h
#protocol RadioCellDelegate <NSObject>
-(void) myRadioCellDelegateDidCheckRadioButton:(RadioCell*)checkedCell;
#end
#interface RadioCell : UITableViewCell
-(void) unCheckRadio;
#property (nonatomic, weak) id <RadioCellDelegate> delegate;
#end
This is the .m file of RadioCell
//RadioCell.m
#property (nonatomic, assign) UIButton myRadio;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString*)reuseIdentifier
_myRadio = [UIButton buttonWithType:UIButtonTypeCustom];
[_myRadio setImage:[UIImage imageNamed:#"uncheck"] forState:UIControlStateNormal];
[_myRadio setImage:[UIImage imageNamed:#"check"] UIControlStateSelected];
[_myRadio addTarget:self action:#selector(radioTouched)orControlEvents:UIControlEventTouchUpInside];
_myRadio.isSelected = NO;
//don't forget to set _myRadio frame
[self addSubview:_myRadio];
}
-(void) checkRadio {
_myradio.isSelected = YES;
}
-(void) unCheckRadio {
_myradio.isSelected = NO;
}
-(void) radioTouched {
if(_myradio.isSelected == YES) {
return;
}
else {
[self checkRadio]
[_delegate myRadioCellDelegateDidCheckRadioButton:self];
}
}
Now just adapt your tableview controller with RadioCell (in .m file)
//MyTableViewController.m
#interface MyTableViewController () <RadioCellDelegate>
#end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"RadioCell";
RadioCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[RadioCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel = #"Coke"; //or whatever you want
cell.delegate = self;
return cell;
}
-(void) myRadioCellDelegateDidCheckRadioButton:(RadioCell*)checkedCell {
NSIndexPath *checkPath = [self.tableView indexPathForCell:checkedCell];
for (int section = 0; section < [self.tableView numberOfSections]; section++) {
if(section == checkPath.section) {
for (int row = 0; row < [self.tableView numberOfRowsInSection:section]; row++) {
NSIndexPath* cellPath = [NSIndexPath indexPathForRow:row inSection:section];
RadioCell* cell = (CustomCell*)[tableView cellForRowAtIndexPath:cellPath];
if(checkPath.row != cellPath.row) {
[cell unCheckRadio];
}
}
}
}
}
Simple solution for a 2-option radio button UITableView (but you get the idea):
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSIndexPath *newIP;
if (!indexPath.row)
newIP = [NSIndexPath indexPathForRow:indexPath.row+1 inSection:0];
else
newIP = [NSIndexPath indexPathForRow:indexPath.row-1 inSection:0];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (cell.accessoryType == UITableViewCellAccessoryCheckmark)
cell.accessoryType = UITableViewCellAccessoryNone;
else{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:newIP];
newCell.accessoryType = UITableViewCellAccessoryNone;
}
}
One solution would be to make use of the table views native selection capabilities.
In a standard UITableView it's only possible to have one row selected at a time and you can use this to your advantage. By setting "Selection" in storyboard to "None" the selection of a row will not be visible.
Now you can implement your own selection display. You can override the method -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath to update your cell when it gets selected.
And you can override the method -(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath to change the cell when it's no longer selected.
UITableViewDelegate automatically calls didSelectRowAtIndexPath on the old selection, when a new selection is made, keeping the selection unique like radio buttons.
I put together a little sample project for you to try, you can download it here.
Hopefully, I have been at least a bit helpful.
Cheers!
I'm working on something that plays an sound when a cell is selected in a table view. The tableView is populated by an Array of NSStrings with sound names.
I have an object as part of the controller that has a sound object, and when you click the cell it plays the sound objects correct file for the name.
How can I have a TableView cell pre selected when the view first loads, and have it so it doesn't play the sound when the tableView first loads.
Here's my selection code as of now.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TMVSoundCell *cell = [tableView dequeueReusableCellWithIdentifier:#"SoundCell" forIndexPath:indexPath];
[self configureCell:cell forRowAtIndexPath:indexPath];
return cell;
}
- (void)configureCell:(TMVSoundCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.soundTitle.text = [[self.soundArray objectAtIndex:indexPath.row]name];
if (self.selectedRowIndexPath != nil && [indexPath compare:self.selectedRowIndexPath] == NSOrderedSame)
{
cell.soundTitle.textColor = [UIColor whiteColor];
self.itemView.item.sound = [self.soundArray objectAtIndex:indexPath.row];
[DataManager saveContext];
}
else
{
cell.soundTitle.textColor = AppDelegate.atmosphere.currentColor;
}
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *indexPaths = [NSMutableArray arrayWithObject:indexPath];
if (self.selectedRowIndexPath)
{
if ([indexPath compare:self.selectedRowIndexPath] == NSOrderedSame)
{
self.selectedRowIndexPath = nil;
}
else
{
[indexPaths addObject:self.selectedRowIndexPath];
self.selectedRowIndexPath= indexPath;
}
}
else
{
self.selectedRowIndexPath = indexPath;
}
[tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
}
Use selectRowAtIndexPath to select a cell programmatically.
According to the docs:
Calling this method does not cause the delegate to receive a
tableView:willSelectRowAtIndexPath: or
tableView:didSelectRowAtIndexPath: message, nor will it send
UITableViewSelectionDidChangeNotification notifications to observers.
You need to call selectRowAtIndexPath in the cellForRowAtIndexPath. An example in Swift4 :
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if ADD_YOUR_CONDITION_HERE {
self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .top)
}
return cell
}
In viewDidAppear add this code (in viewDidAppear to make sure your tableView has loaded)
UITableViewCell *cellToSelect = [self.tableView cellForRowAtIndexPath:self.selectedRowIndexPath];
[cellToSelect setHighlighted:YES animated:YES];
Your cell will be selected without calling the delegate method, so the sound won't be played.
You can call the
- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition
method in your viewDidLoad or viewWillAppear and it will select the row and not call your didSelectRowDelegate
Hope that helps
Cong
[self.soundArray enumerateObjectsUsingBlock:^(Sound *obj, NSUInteger idx, BOOL *stop)
{
if ([obj.name isEqualToString:self.itemView.item.sound.name])
{
[self tableView:self.tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:idx inSection:0]];
}
}];
I'm such an idiot. I never thought of putting this in the my conifgureView method.
Thank you to everyone who helped me out.
In your cellForRowAtIndexPath method, check to see if the cell is for the row you want to be selected. If it is, set it selected. cell.selected = YES
Alternatively, [self.tableView selectRowAtIndexPath:self.selectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[self.tableView didSelectRowAtIndexPath:self.selectedIndexPath];`
I have make multiple selection like below in UITableViewController using. This is delegate method of UIAlertView, in which there is UIAlertView contains Table.
Now How can I add particular selected indexPath.row to my array and remove if some one uncheck it.
- (void)tableAlert:(SBTableAlert *)tableAlert didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"MY INDEX PATH IS %#", indexPath);
if (tableAlert.type == SBTableAlertTypeMultipleSelct) {
UITableViewCell *cell = [tableAlert.tableView cellForRowAtIndexPath:indexPath];
if (cell.accessoryType == UITableViewCellAccessoryNone)
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
else
[cell setAccessoryType:UITableViewCellAccessoryNone];
[tableAlert.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
}
I would be tempted to create a dictionary of your items used for the tableView. Then have a 'selected' bool property for each object in the dictionary. You can then simply turn this true or false and use [tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone]; when your alert view is tapped. You could then also enumerate through if you needed to know which objects have their rows selected.