I have a UITableView in a UIViewController. It's Selection modes are set to Single and Multiple Selection During Editing.
When I set the tableView's editing property to YES, if a process asynchronously runs reloadData, any of my current check marks that have been selected disappear. I implemented my tableView:cellForRowAtIndexPath: method such that it sets the selected property of the cell, based on what the selection was before the reloadData. I've verified those are being set correctly. But to no avail, they still don't show up selected. What do I need to change so that it gets that set correctly? It seems to retain the indented editing mode just fine.
Here's said method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ValveCell";
ValveCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Valve *valve = self.sections[indexPath.section][indexPath.row];
cell.subject = valve;
cell.selected = [self.selections member: valve];
return cell;
}
I use the didSelect and didDeselect methods to keep selections (an NSMutableSet) up to date. E.g.
- (void) tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
NSMutableArray *section = self.sections[indexPath.section];
[self.selections removeObject: section[indexPath.row]];
}
Clarification
Given the answers, I think I did not clarify which check marks I was referring to. I am referring to the blue check marks that show a selected state on the left side of the table when the table has had its editing set to YES as shown in this picture:
If a reloadData occurs, it will clear those blue check marks.
Update: My Current Solution
What I did to retain the visual selection status is to modify the KVO method that was firing the reloadData to include code to programmatically reselect the current selections:
...
[self.tableView reloadData];
if (self.tableView.editing) {
[self.sections enumerateObjectsUsingBlock:^(id valves, NSUInteger section, BOOL *stop) {
[(NSArray*)valves enumerateObjectsUsingBlock:^(id valve, NSUInteger row, BOOL *stop) {
if ([self.selections member: valve]) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow: row inSection: section];
[self.tableView selectRowAtIndexPath: indexPath animated: NO scrollPosition: UITableViewScrollPositionNone];
}
}];
}];
}
...
The selectRowAtIndexPath:animated:scrollPosition: method does the work.
So at this point, I guess my question is, do I really have to do this? Or I'm working too hard?
Setting the cell to "selected" in cellForRowAtIndexPath won't force a call to the UITableView delegate methods so if that's where you're setting your checkmarks, they won't automatically appear upon the table view's reload.
You can either add the checkmarks directly in cellForRowAtIndexPath, ex:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ValveCell";
ValveCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Valve *valve = self.sections[indexPath.section][indexPath.row];
cell.subject = valve;
cell.selected = [self.selections member: valve];
if (cell.selected) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
Or you can call selectRowAtIndexPath manually, ex:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ValveCell";
ValveCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Valve *valve = self.sections[indexPath.section][indexPath.row];
cell.subject = valve;
cell.selected = [self.selections member: valve];
if (cell.selected) {
[self tableView:tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
return cell;
}
Related
I am new to Objective C. I am having the UITableView in that I have to select the tableview cell from top to bottom also without selecting the first cell the second cell won't be selected and the user won't able to select the in-between cells also.can anyone help me how to solve this?
I added the example image here if the user selected the first cell the second cell want to enable and if the user selects the second cell the third cell want to enable remaining cells want to disable.
- (void)viewDidLoad {
[super viewDidLoad];
self.colors = [[NSArray alloc] initWithObjects: #"Red", #"Yellow", #"Green",
#"Blue", #"Purpole", nil];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.colors count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"cell";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.text = [self.colors objectAtIndex:indexPath.row];
return cell;
}
Your question is not clear, Hope you have an issue with making multiple cell selection:
Allow UITableView to select multiple cells by tableView.allowsMultipleSelection = true;. And then a user can select any number of cells in the table.
Using this way you can able to select the current row and enable next row for selecting. You need to handle another way to disable others row in cellForRowAtIndexPath method.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.checkMark.selected = YES;
NSString *cellText = cell.textLabel.text;
if(self.colors.count < indexPath.row+1 ){
NSIndexPath *nextIndexPath = [NSIndexPath indexPathForRow:indexPath.row+1 inSection:0];
UITableViewCell *nextCell = [tableView cellForRowAtIndexPath:nextIndexPath];
nextCell.checkMark.userInteractionEnabled = YES;
//OR
nextCell.userInteractionEnabled = YES;
}
}
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];
}
All I'm looking to do is get the selected (checkmarked) rows from my UITableView and show them in my console log. Doesn't seem like it should be so difficult. I've found two methods that I'll display below. Neither work despite the logic mostly making sense to me. Which would you suggest and how can I tweak to make it work?
My TableView Code:
I don't think this is completely necessary to the issue, but I know it sometimes helps to see the whole picture.
#pragma mark - tableView datasource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.places count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSDictionary *tempDictionary= [self.places objectAtIndex:indexPath.row];
cell.textLabel.text = [tempDictionary objectForKey:#"name"];
if([tempDictionary objectForKey:#"vicinity"] != NULL)
{
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#",[tempDictionary objectForKey:#"vicinity"]];
}
else
{
cell.detailTextLabel.text = [NSString stringWithFormat:#"Address Not Available"];
}
return cell;
}
//Handles tableView row selection and addition and removal of the checkmark
- (void)tableView:(UITableView *)theTableView
didSelectRowAtIndexPath:(NSIndexPath *)newIndexPath {
[theTableView deselectRowAtIndexPath:[theTableView indexPathForSelectedRow] animated:NO];
UITableViewCell *cell = [theTableView cellForRowAtIndexPath:newIndexPath];
if (cell.accessoryType == UITableViewCellAccessoryNone) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
// Reflect selection in data model
} else if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
cell.accessoryType = UITableViewCellAccessoryNone;
// Reflect deselection in data model
}
}
Method 1:
Add a conditional statement to the end of the checkmark handler to add/remove selections to and from an array. Then create a button action that simply calls the array and displays it in the console. I think this is clunky but could work.
//Handles tableView row selection and addition and removal of the checkmark
- (void)tableView:(UITableView *)theTableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[theTableView deselectRowAtIndexPath:[theTableView indexPathForSelectedRow] animated:NO];
UITableViewCell *cell = [theTableView cellForRowAtIndexPath:indexPath];
if (cell.accessoryType == UITableViewCellAccessoryNone) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
//Reflect selection in data model
} else if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
cell.accessoryType = UITableViewCellAccessoryNone;
//Reflect deselection in data model
}
if ([[theTableView cellForRowAtIndexPath:indexPath] accessoryType] == UITableViewCellAccessoryCheckmark) {
[_selectedCellIndexes addObject:indexPath];
}
}
- (IBAction)sendResults:(id)sender {
NSLog(#"Add these: %#", _selectedCellIndexes);
}
Method 2:
Get the selected rows AND send to console log only when button is tapped. This seems to be the more logical method, but I can't seem to get it to work either. It doesn't throw any errors, but returns "Selected Items: (null)" in the console. What have I missed?
//Sends checkmarked items to console log
- (IBAction)sendResultsOption1:(id)sender {
NSMutableArray *aList = [[NSMutableArray alloc] init];
for (NSIndexPath *indexPath in _tableView.indexPathsForSelectedRows) {
NSString *r = [NSString stringWithFormat:#"%li",(long)indexPath.row];
[aList addObject:r];
}
NSLog(#"Selected Items: %#", _aList);
}
For what it's worth, I've also followed the instructions here without any luck. Hope you guys can help. Thanks in advance!
In Method 1, your method looks like this:
- (void)tableView:(UITableView *)theTableView
didSelectRowAtIndexPath:(NSIndexPath *)newIndexPath {
But you're referring to indexPath in the body. You don't have an indexPath (the undeclared identifier), but you have a newIndexPath, so at a minimum, this is the start of your problems and should be fixed first.
Giving your variables the right names looks like it should work for this approach...
In Method 2, the problem is none of your table view cells are selected. In you didSelectRowAtIndexPath method, you check the accessory icon to a check mark, then you deselect the row. So there are no objects in _tableView.indexPathsForSelectedRows.
In this approach, you need to change your for loop. Instead you need to iterate through every index path, and check on the accessory icon. If it's a check mark, add it to the array. Now log the array.
As far as which approach would be preferable, it depends on how you intend to use this ultimately. Obviously, the end goal isn't to NSLog the checkmarked rows--this is an iOS app we're talking about.
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.
I am having issues with my tableView not firing the didSelectRowAtIndexPath method. I have implemented the delegates as such:
#interface ViewController : UIViewController<UITableViewDataSource, UITableViewDelegate,UIScrollViewDelegate>
And in my storyboard the tableView's data source and delegate are both pointed at the base View Controller. I have User Interactions enabled as well as Selection set to Single Selection, and it is not the TapGesture problem since my tap gestures are not bound to the view and I have checked and they do not fire.
This is the code for setting up the table:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return menuArray.count;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"menuCell"];
NSDictionary *menuItem = [menuArray objectAtIndex:indexPath.row];
cell.textLabel.text = menuItem[#"Title"];
cell.detailTextLabel.text = menuItem[#"Subtitle"];
return cell;
}
-(void)showMenu{
[UIView animateWithDuration:.25 animations:^{
[content setFrame:CGRectMake(menuTable.frame.size.width, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}];
}
-(void)hideMenu{
[UIView animateWithDuration:.25 animations:^{
[content setFrame:CGRectMake(0, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}];
}
-(IBAction)showMenuDown:(id)sender {
if(content.frame.origin.x == 0)
[self showMenu];
else
[self hideMenu];
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//whatever
}
The table is initially out of view on the storyboard (origin.x is set to -150), then when the user clicks on a button in the navigationBar, the view slides over to reveal it, which is what might be causing the problem I think.
Is there anything wrong with my code or implementation that would be causing this to not work?
If you already see your table populated with values from your dictionary then you can rule out data source and delegate as being the problem. i.e. your storyboard connections are working.
Your code looks fine to me. the only difference I see is I usually define my table like this. Try this and see if it helps.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//NSLog(#"Inside cellForRowAtIndexPath");
static NSString *CellIdentifier = #"Cell";
// Try to retrieve from the table view a now-unused cell with the given identifier.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// If no cell is available, create a new one using the given identifier.
if (cell == nil)
{
// Use the default cell style.
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
//Your code here
// ....
return cell;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"menuCell"];
This will return nil in case there was never a cell created.
so checking if cell is nil is mandatory and if so, you need to create a cell.
static NSString *CellIdentifier = #"menuCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
}
as you are using storyboard you can alternatively use
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
for prototype cells. Make sure you use the same identifier in the storyboard and that you registered your the cell's class
- (void) viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"menuCell"];
}