I have certain cells/rows on my tableview that should be set to 'selected' when the view is opened. In my code below, if the data source contains a specific user id, a green check mark should appear (this part works), and the row should be 'Selected'. That said, the selected state doesn't seem to work. How can I set a cell as selected?
ViewController.m
-(UITableViewCell *)tableView:(UITableView*)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
NSDictionary *client = self.sectionClients[indexPath.section][indexPath.row];
static NSString *ClientTableIdentifier = @"ClientTableViewCell";
ClientTableViewCell *cell = (ClientTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:ClientTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"ClientTableViewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSString *photo = client[@"client photo"];
cell.clientName.text = [NSString stringWithFormat:@"%@ %@", client[@"first name"], client[@"last name"]];
cell.subtext.text = client[@"phone"];
NSURL *imageUrl = [NSURL URLWithString:photo];
[cell.clientPhoto setImageWithURL:imageUrl];
if (self.selectionData != NULL && [[self.selectionData valueForKey:@"clients ids"] containsString:client[@"nid"]])
{
cell.greenCheck.image = [UIImage imageNamed:@"added.png"];
[cell setSelected:YES];
}
return cell;
}
CodePudding user response:
Calling setSelected
on a cell only sets the selected state for that cell, not for a row in the table. The selected state for a cell simply affects its appearance. It does not affect the selection state of the row itself.
Since cells can be re-used, your cellForRowAtIndexPath
function should also clear the selected state if the cell is being used for a row that should not be selected.
In order to put a row
into the selected state, you will also need to call selectRowAtIndexPath
for each row that you want to be selected, even ones that are not currently displayed. So you should not call it from cellForRowAtIndexPath
because that will only select rows that have actually been displayed.
I am not sure when the best time would be to call selectRowAtIndexPath
. It would have to be after the table view has called numberOfRowsInSection
for each section in the table. You can try selecting the rows from viewDidAppear
. If the table is not ready at that time, then you may have to keep track of when numberOfRowsInSection
has been called for every section.
CodePudding user response:
Lots to discuss, but to give you a couple ideas...
First, you need to inform the table view that a row is selected using selectRowAtIndexPath
.
One way to do that is to pass the row(s) you want pre-selected to the table view controller and then selecting the rows in viewDidLayoutSubviews
. Note that viewDidLayoutSubviews
is called many times throughout the controller's life-cycle, so you'll only want to do this once.
Also, you want to make sure you are tracking the selected state in your data source.
Assuming we pass an array of NSNumber
:
@interface ClientTableViewController : UITableViewController
@property (strong, nonatomic) NSArray *preSelectedRows;
@end
and using a very simple object model like:
@interface ClientObject : NSObject
@property (strong, nonatomic) NSString *name;
@property (assign, readwrite) BOOL selected;
@end
you would set the .selected
property in viewDidLoad()
:
// set .selected property for rows we're going to pre-select
for (NSNumber *n in _preSelectedRows) {
clientData[[n integerValue]].selected = YES;
}
and then, in viewDidLayoutSubviews
:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// viewDidLayoutSubviews is called many times during controller lifecycle
// so we only want to do this ONCE
if (_preSelectedRows) {
for (NSNumber *n in _preSelectedRows) {
NSIndexPath *p = [NSIndexPath indexPathForRow:[n integerValue] inSection:0];
[self.tableView selectRowAtIndexPath:p animated:NO scrollPosition:UITableViewScrollPositionNone];
}
// clear the array so we don't call this again
_preSelectedRows = nil;
}
}
You can also save yourself a lot of effort by having your cell class handle the visual representation. Something like this:
@interface ClientTableViewCell()
{
UIImage *selectedImg;
UIImage *unselectedImg;
}
@end
@implementation ClientTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
selectedImg = [UIImage systemImageNamed:@"checkmark.square"];
unselectedImg = [UIImage systemImageNamed:@"square"];
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
self.textLabel.textColor = selected ? [UIColor redColor] : [UIColor blackColor];
self.imageView.image = selected ? selectedImg : unselectedImg;
}
@end
and your cellForRowAt
becomes simply:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ClientTableViewCell *cell = (ClientTableViewCell *)[tableView dequeueReusableCellWithIdentifier:clientTableIdentifier forIndexPath:indexPath];
// we only need to set the name, because the cell class will handle
// visual appearance when selected
cell.textLabel.text = clientData[indexPath.row].name;
return cell;
}
Here is a complete example: https://github.com/DonMag/PreSelectExample