可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I've tried literally everything and anything I could think of. Can anyone figure out why the UITableViewCell
s don't reload the checkmarks after selecting, scrolling away, then scrolling back?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { #define CHECK_NULL_STRING(str) ([str isKindOfClass:[NSNull class]] || !str)?@"":str static NSString *CellIdentifier = @"inviteCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; cell.accessoryType = UITableViewCellAccessoryCheckmark; cell.textLabel.highlightedTextColor = [UIColor colorWithHexString:@"#669900"]; cell.selectionStyle = UITableViewCellSelectionStyleGray; cell.backgroundColor = [UIColor blackColor]; cell.textLabel.textColor = [UIColor whiteColor]; [[UITableViewCell appearance] setTintColor:[UIColor colorWithHexString:@"#669900"]]; if (cell == nil) { cell = [[UITableViewCell alloc] init]; } if (cell == nil) {cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; } BOOL isSearching = tableView != self.tableView; NSArray *arrayToUse = (isSearching ? searchResults : contactsObjects); id p = arrayToUse[indexPath.row]; NSString *fName = (__bridge_transfer NSString *)(ABRecordCopyValue((__bridge ABRecordRef)(p), kABPersonSortByFirstName)); NSString *lName = (__bridge_transfer NSString *)(ABRecordCopyValue((__bridge ABRecordRef)(p), kABPersonSortByLastName)); cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", CHECK_NULL_STRING(fName), CHECK_NULL_STRING(lName)]; BOOL showCheckmark = [[stateArray objectAtIndex:indexPath.row] boolValue]; if (showCheckmark == YES) { cell.accessoryType = UITableViewCellAccessoryCheckmark; NSLog(@"It hit showCheckmark = YES, and stateArray is %@",stateArray[indexPath.row]); } else { cell.accessoryType = UITableViewCellAccessoryNone; NSLog(@"It hit showCheckmark = NO, and stateArray is %@",stateArray[indexPath.row]); } return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath; { id object = contactsObjects[indexPath.row]; UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; if (cell.accessoryType == UITableViewCellAccessoryNone) { cell.accessoryType = UITableViewCellAccessoryCheckmark; [stateArray insertObject:[NSNumber numberWithBool:YES] atIndex:indexPath.row]; [selectedObjects addObject:object]; } else { cell.accessoryType = UITableViewCellAccessoryNone; [stateArray insertObject:[NSNumber numberWithBool:NO] atIndex:indexPath.row]; [selectedObjects removeObject:object]; } //slow-motion selection animation. [tableView deselectRowAtIndexPath:indexPath animated:YES]; }
回答1:
You missed out the !
(inverse operator) on the following line meaning that the state will always be the same.
[stateArray replaceObjectAtIndex:indexPath.row withObject:[NSNumber numberWithBool:[[stateArray objectAtIndex:indexPath.row] boolValue]]];
It should be
[stateArray replaceObjectAtIndex:indexPath.row withObject:[NSNumber numberWithBool:![[stateArray objectAtIndex:indexPath.row] boolValue]]];
Edit --- I've refactored both methods because it can be done with a lot less code and it will completely simplify the methods for you.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"inviteCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; BOOL isSearching = tableView != self.tableView; NSArray *arrayToUse = (isSearching ? searchResults : contactsObjects); id p = arrayToUse[indexPath.row]; NSString *fName = (__bridge_transfer NSString *)(ABRecordCopyValue((__bridge ABRecordRef)(p), kABPersonSortByFirstName)); NSString *lName = (__bridge_transfer NSString *)(ABRecordCopyValue((__bridge ABRecordRef)(p), kABPersonSortByLastName)); cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", CHECK_NULL_STRING(fName), CHECK_NULL_STRING(lName)]; BOOL showCheckmark = [stateArray[indexPath.row] boolValue]; if (showCheckmark == YES) { cell.accessoryType = UITableViewCellAccessoryCheckmark; } else { cell.accessoryType = UITableViewCellAccessoryNone; } return cell; } - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { id object = contactsObjects[indexPath.row]; UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; if (cell.accessoryType == UITableViewCellAccessoryNone) { cell.accessoryType = UITableViewCellAccessoryCheckmark; [selectedObjects addObject:object]; } else { cell.accessoryType = UITableViewCellAccessoryNone; [selectedObjects removeObject:object]; } stateArray[indexPath.row] = @(cell.accessoryType == UITableViewCellAccessoryCheckmark); }
回答2:
I suggest a more object oriented approach. This will ensure that your code is flexible and displays correctly all the time.
For each item you wish to display in your table, have a corresponding object. You mentioned that you are displaying contacts, so let's suppose your object is called "Contact":
//Contact.h @interface Contact : NSObject @property BOOL selected; @property NSString *name; @end //Contact.m #import Contact.h @implementation Contact + (id) contactWithName:(NSString*)name { Contact *nContact = [Contact new]; nContact.name = name; nContact.selected = NO; return nContact; } @end
Then, just make your view work something like this:
//ContactView.m @interface ContactView() @property NSMutableArray *contacts; @end @implementation ContactView @synthesize contacts; - (void) viewDidLoad { [super viewDidLoad]; //get your contact list here. When creating contacts, be sure to assign their selected and their name as you require. contacts = @[[Contact contactWithName:@"John"], [Contact contactWithName:@"Jane"]]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *cellID = @"inviteCell"; UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:cellID]; if (cell == nil) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID]; Contact *cellContact = [contacts objectAtIndex:indexPath.row]; cell.textLabel.text = cellContact.name; cell.accessoryType = cellContact.selected == YES ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { Contact *cellContact = [contacts objectAtIndex:indexPath.row]; cellContact.selected = !cellContact.selected; [contacts replaceObjectAtIndex:indexPath.row withObject:cellContact]; [tableView reloadData]; //to refresh without animation //[tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [tableView numberOfSections])] withRowAnimation:UITableViewRowAnimationTop]; //to refresh with animation } @end
And boom, easy to use tables that always look right, queue properly, and are object oriented for easy maintenance later.
回答3:
The cell selection problem was not solved, even when insertObject
was replaced with replaceWithObject
, however, one should not waste time setting BOOL
objects with an NSInteger
inside an NSMutableArray
. Instead, for cell selection memory, one should use NSDictionary
like this:
@property (nonatomic, strong) NSMutableDictionary * selectedRowCollection; - (void)viewDidLoad{ self.selectedRowCollection = [[NSMutableDictionary alloc] init]; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath; { id object = contactsObjects[indexPath.row]; UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; if (cell.accessoryType == UITableViewCellAccessoryNone) { cell.accessoryType = UITableViewCellAccessoryCheckmark; [self.selectedRowCollection setObject:@"1" forKey:[NSString stringWithFormat:@"%d",indexPath.row]]; } else { cell.accessoryType = UITableViewCellAccessoryNone; [self.selectedRowCollection removeObjectForKey:[NSString stringWithFormat:@"%d",indexPath.row]]; } //slow-motion selection animation. [tableView deselectRowAtIndexPath:indexPath animated:YES]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { BOOL showCheckmark = [[self.selectedRowCollection valueForKey:[NSString stringWithFormat:@"%d",indexPath.row]] boolValue]; if (showCheckmark == YES) { cell.accessoryType = UITableViewCellAccessoryCheckmark; } else { cell.accessoryType = UITableViewCellAccessoryNone; } }
If this answer helped you at all, remember to bump this question up! Thanks!
回答4:
You are not doing this UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:cellID]; if (cell == nil) {cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID]; }
Allocating is necessary if cell is nil. Or it cause problem while scrolling.