I do check in didSelectRow Method, but as I move up the table, it removes the check
I think its creating new Cell
How can I use unique cell so that it would not replace the checked cell to unchecked.
- (void)tableAlert:(SBTableAlert *)tableAlert didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"MY INDEX PATH IS %#", indexPath);
NSString *email = [allEmails objectAtIndex:indexPath.row];
if (tableAlert.type == SBTableAlertTypeMultipleSelct) {
UITableViewCell *cell = [tableAlert.tableView cellForRowAtIndexPath:indexPath];
if (cell.accessoryType == UITableViewCellAccessoryNone){
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
[selectedEmail addObject:email];
}
else{
[cell setAccessoryType:UITableViewCellAccessoryNone];
[selectedEmail removeObject:email];
}
[tableAlert.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
NSLog(#"Final Array is %#", selectedEmail);
}
- (UITableViewCell *)tableAlert:(SBTableAlert *)tableAlert cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell;
// NSString *identifier = [NSString stringWithFormat:#"%d%d", indexPath.section, indexPath.row];
if (tableAlert.view.tag == 0 || tableAlert.view.tag == 1) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
} else {
// Note: SBTableAlertCell
cell = [[SBTableAlertCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
}
//[cell.textLabel setText:[NSString stringWithFormat:#"Cell %d", indexPath.section]];
NSString *email = [allEmails objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%#", email];
UIFont *myFont = [ UIFont fontWithName: #"Arial" size: 14.0 ];
cell.textLabel.font = myFont;
return cell;
}
Since you are already storing the selected emails in selectedEmail array, you can make use of it in the cellForRowAtIndexPath method to display the checkmark:
NSString *email = [allEmails objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%#", email];
if ([selectedEmail indexOfObject:email] != NSNotFound) {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
else {
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
In this way, even if the cell is reused, there will not be any issue.
Related
When my user searches for something inside a tableview, when no results are returned, my app shows the standard "No Results" placeholder inside the tableview. That said, when no results exist, I want to return one populated cell (cell populated with default data). How can I accomplish this? I tried the below, but I still get 'No Results' returned?
ViewController.m
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == self.searchDisplayController.searchResultsTableView) {
if ([searchResults count] == 0) {
return 1;
} else {
return [searchResults count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *NetworkTableIdentifier = #"sidebarCell";
self.sidetableView.separatorStyle = UITableViewCellSeparatorStyleNone;
sidebarCell *cell = (sidebarCell *)[tableView dequeueReusableCellWithIdentifier:NetworkTableIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"sidebarCell" owner:self options:nil];
cell = nib[0];
}
if (tableView == self.searchDisplayController.searchResultsTableView) {
NSDictionary *userName = searchResults[indexPath.row];
NSString *first = userName[#"first name"];
NSString *last = userName[#"last name"];
[[cell username] setText:[NSString stringWithFormat:#"%# %#", first, last]];
NSDictionary *userlast = searchResults[indexPath.row];
[[cell lastName] setText:userlast[#"last name"]];
NSDictionary *userBio = searchResults[indexPath.row];
[[cell userDescription] setText:userBio[#"userbio"]];
NSString *area = userName[#"neighbourhood"];
NSString *city = userName[#"city"];
[[cell areaLabel] setText:[NSString stringWithFormat:#"%#, %#", area, city]];
NSString *profilePath = searchResults[indexPath.row][#"photo_path"];
[cell.usermini sd_setImageWithURL:[NSURL URLWithString:profilePath]];
if ([searchResults count] == 0) {
NSLog(#"SEARCH RESULTS ARE %#", searchResults);
[[cell username] setText:[NSString stringWithFormat:#"%#", self.searchBar.text]];
[[cell lastName] setText:userlast[#""]];
[[cell userDescription] setText:#"This friend is not on the app (yet!) Tap to invite them."];
[[cell areaLabel] setText:[NSString stringWithFormat:#""]];
NSString *profileDefault = #"http://url.com/user.png";
[cell.usermini sd_setImageWithURL:[NSURL URLWithString:profileDefault]];
return cell;
}
return cell;
}
I don't really recommend doing this, as you should return an empty list, if there are no search results. That is consistent with User Interface Guidelines. But, if you insist, you could create a default object and initialize your searchResults array with that object and return 1 from the numberOfRows method. Something like this:
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.searchDisplayController.searchResultsTableView) {
if ([searchResults count] == 0) {
NSDictionary *dict = [NSDictionary dictionaryWithObjects:#[#“Enter First Name”, #“Enter Last Name”, #“Enter User Bio”, #“Enter Neighborhood”, #“Enter City”, #“Enter Photo Path”]
forKeys: #[#“first_name”, #“last_name, #“userbio”, #“neighbourhood”, #“city”, #“photo_path”];
searchResults = [NSArray arrayWithObjects: dict, nil];
return 1;
}
else {
return [searchResults count];
}
}
And, you can greatly simplify your cellForRowAtIndexPath code as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *NetworkTableIdentifier = #"sidebarCell";
self.sidetableView.separatorStyle = UITableViewCellSeparatorStyleNone;
sidebarCell *cell = (sidebarCell *)[tableView dequeueReusableCellWithIdentifier:NetworkTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"sidebarCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
if (tableView == self.searchDisplayController.searchResultsTableView) {
//Note, I like to name my local variables the same as the dictionary keys just to eliminate any confusion
NSDictionary *userObject = [searchResults objectAtIndex:indexPath.row];
NSString *first_name = [userObject objectForKey:#"first name"];
NSString *last_name = [userObject objectForKey:#"last name"];
NSString *userbio = [userObject objectForKey:#“userbio”];
NSString *neighbourhood = [userObject objectForKey:#“neighbourhood”];
NSString *city = [userObject objectForKey:#“city”];
NSString *photo_path = [userObject objectForKey:#“photo_path”];
[[cell username] setText:[NSString stringWithFormat:#"%# %#", first_name, last_name]];
[[cell lastName] setText:last_name];
[[cell userDescription] setText:userbio];
[[cell areaLabel] setText:[NSString stringWithFormat:#"%#, %#", neighbourhood, city]];
[[cell usermini] sd_setImageWithURL:[NSURL URLWithString:photo_path]];
}
return cell;
}
I did something like this in my app. It's ugly and I'm not recommending you following this way. I did it only because I was kind of lazy about layouts and placing the placeholder in the correct place in the view hierarchy and handle all those hide/show situations. My view controller has a very complex hierarchy of views and the table view was one that has already had all I needed (resize automatically when the status or toolbar is showing).
What I suggest you is to hide the table view when there is an empty search result and substitute it with your placeholder.
Try this it's working:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSInteger numOfSections = 0;
if (arrData.count>0)
{
self.TblView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
numOfSections = 1;
self.TblView.backgroundView = nil;
}
else
{
UILabel *noDataLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.TblView.bounds.size.width, self.TblView.bounds.size.height)];
noDataLabel.text = #"No Results";
noDataLabel.textColor = [UIColor blackColor];
noDataLabel.textAlignment = NSTextAlignmentCenter;
self.TblView.backgroundView = noDataLabel;
self.TblView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
return numOfSections;
}
I have the following code to populate data on the uitableview cell. For some reason, I see every item exactly two on the tableview.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"takeStockCell";
UITableViewCell * cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
RackStockTakeStatus *rackStockTakeStatus = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = rackStockTakeStatus.locName;
if ([[rackStockTakeStatus.status lowercaseString] isEqualToString:#"inprogress"])
{
cell.detailTextLabel.textColor = [UIColor blueColor];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%# [%#]",rackStockTakeStatus.status,rackStockTakeStatus.stockTakeByUser];
} else if ([[rackStockTakeStatus.status lowercaseString] isEqualToString:#"completed"])
{
cell.detailTextLabel.textColor = [UIColor colorWithRed:(0/255.0) green:(102/255.0) blue:(0/255.0) alpha:1];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%# [%#]",rackStockTakeStatus.status,rackStockTakeStatus.stockTakeByUser];
} else if ([[rackStockTakeStatus.status lowercaseString] isEqualToString:#"verified"])
{
cell.detailTextLabel.textColor = [UIColor colorWithRed:(0/255.0) green:(102/255.0) blue:(0/255.0) alpha:1];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%# [%#]",rackStockTakeStatus.status,rackStockTakeStatus.stockTakeByUser];
}
}
I am not sure about your method configuring the cell like that. Have you tried to configure it directly into the method cellForRowAtIndexPath ? Not putting the cell as a parameter.
Or just returning the cell in your method configure
cell = [self configureCell...
Why the UITableViewCells 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];
}
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);
}
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.
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;
}
}
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.
i have a question regarding NSUserDefaults. I am trying to save the checkmark that i place on a cell and then retrieve it when the app crashes or when user closes app and so on. I tried to use this post post as a guide, but no luck, but here's what i have so far. The code from the post works, however, i only need one checkmark to be saved rather than many. How would i achieve this?
#implementation ClientsViewController
#synthesize clientsInt; // This is just a variable that helps me do the drill down part of the rootviewcontroller
#synthesize checkedIndexPath;
- (NSString *)getKeyForIndex:(int)index
{
return [NSString stringWithFormat:#"KEY%d",index];
}
- (BOOL) getCheckedForIndex:(int)index
{
if([[[NSUserDefaults standardUserDefaults] valueForKey:[self getKeyForIndex:index]] boolValue]==YES)
{
return YES;
}
else
{
return NO;
}
}
- (void) checkedCellAtIndex:(int)index
{
BOOL boolChecked = [self getCheckedForIndex:index];
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:!boolChecked] forKey:[self getKeyForIndex:index]];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
if([self getCheckedForIndex:indexPath.row]==YES)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
// Configure the cell...
if (clientsInt == 0) {
cell.textLabel.text = [array1 objectAtIndex:indexPath.row];
}
else if (clientsInt == 1) {
cell.textLabel.text = [array2 objectAtIndex:indexPath.row];
}
else if (clientsInt == 2) {
cell.textLabel.text = [array3 objectAtIndex:indexPath.row];
}
else if (clientsInt == 3) {
cell.textLabel.text = [array4 objectAtIndex:indexPath.row];
}
if([self.checkedIndexPath isEqual:indexPath])
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Uncheck the previous checked row
if(self.checkedIndexPath)
{
UITableViewCell* uncheckCell = [tableView cellForRowAtIndexPath:self.checkedIndexPath];
uncheckCell.accessoryType = UITableViewCellAccessoryNone;
}
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
self.checkedIndexPath = indexPath;
[self checkedCellAtIndex:indexPath.row];
if([self getCheckedForIndex:indexPath.row]==YES)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Maybe try something like this every time the user checks a row:
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:index] forKey:#"kCheckedBoxKey"];
Since each time, you would save the index under the same key (#"kCheckedBoxKey"), only one index will ever be stored, and it will always be the latest one that the user checked. All you would need to do the next time you load is ask userDefaults if it can find a value for the key #"kCheckedBoxKey", and if so, you would respond by programatically checking the index that was stored.
(you'd of course also want to clean it up by using #define CHECKED_KEY #"kCheckedBoxKey" at the top of the file, and use CHECKED_KEY instead of the literal string to protect against misspellings. At any rate, the point is to make sure you always save & restore using that same key.)
I recently had to save the state of each cell in my table view when the user selected or deselected them to add or remove checkmarks. Here is the snippet of code I used to save to a .plist file (let me know if you need the whole implementation I came up with:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *contentForThisRow = [[self list] objectAtIndex:[indexPath row]];
NSString *CellIdentifier = [NSString stringWithFormat:#"Cell%d%d", indexPath.section, indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSString *documentDirectory = [(AppDelegate *)[[UIApplication sharedApplication] delegate] applicationDocumentsDirectory];
NSString *PlistPath = [documentDirectory stringByAppendingPathComponent:#"Settings.plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:PlistPath];
NSString *row = [NSString stringWithFormat:#"%d",indexPath.row];
if([[dict objectForKey:row]isEqualToString:#"0"])
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
else
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
[[cell textLabel] setText:contentForThisRow];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *documentDirectory = [(AppDelegate *)[[UIApplication sharedApplication] delegate] applicationDocumentsDirectory];
NSString *PlistPath = [documentDirectory stringByAppendingPathComponent:#"Settings.plist"];
NSMutableDictionary *plist = [NSMutableDictionary dictionaryWithContentsOfFile:PlistPath];
UITableViewCell *cell = [self._tableView cellForRowAtIndexPath:indexPath];
NSString *row = [NSString stringWithFormat:#"%d",indexPath.row];
if(cell.accessoryType == UITableViewCellAccessoryNone)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
NSString *on = #"1";
[plist setObject:on forKey:row];
[plist writeToFile:PlistPath atomically:YES];
}
else if(cell.accessoryType == UITableViewCellAccessoryCheckmark)
{
cell.accessoryType = UITableViewCellAccessoryNone;
NSString *off = #"0";
[plist setObject:off forKey:row];
[plist writeToFile:PlistPath atomically:YES];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
You only need to save the index of the selected row
I'm using a tableview with sections and multiple selection, but I have an issue with multiple rows being checked when one row is chosen...
I've seen a few other threads about this, but didn't really get a solution...
Here's my code:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *) indexPath
{
[employeeTable deselectRowAtIndexPath:[employeeTable indexPathForSelectedRow] animated:NO];
UITableViewCell *cell = [employeeTable cellForRowAtIndexPath:indexPath];
// get the letter in each section
NSString *alphabet = [charIndex objectAtIndex:indexPath.section];
// get the names beginning with the letter
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF beginswith[c] %#", alphabet];
NSArray *names = [listOfNames filteredArrayUsingPredicate:predicate];
NSString *name = [names objectAtIndex:indexPath.row];
for(int i = 0; i < [employeeConnection.employees count]; i++)
{
Employee *aEmployee = [employeeConnection.employees objectAtIndex:i];
NSString *firstName = aEmployee.firstName;
NSString *lastName = aEmployee.lastName;
NSString *fullName = [NSString stringWithFormat:#"%# %#", firstName, lastName];
if([fullName isEqualToString:name])
{
NSLog(#"Name: %#", name);
if (cell.accessoryType == UITableViewCellAccessoryNone) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
// Reflect selection in data model
[chosenEmployees addObject:aEmployee.employeeID];
[chosenEmployeesNames addObject:name];
} else if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
cell.accessoryType = UITableViewCellAccessoryNone;
// Reflect deselection in data model
[chosenEmployees removeObject:aEmployee.employeeID];
[chosenEmployeesNames removeObject:name];
}
}
}
}
Update: Added cellForRowAtIndexPath
- (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];
cell.textLabel.textColor = [UIColor whiteColor];
}
// Get the letter in the current section
NSString *alphabet = [charIndex objectAtIndex:[indexPath section]];
// Get the names beginning with the letter
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF beginswith[c] %#", alphabet];
NSArray *names = [listOfNames filteredArrayUsingPredicate:predicate];
if([names count] > 0)
{
// Extract the name
cell.textLabel.text = [names objectAtIndex:indexPath.row];
}
return cell;
}
I would suggest storing an NSMutableSet of either the checked ManagedObject (when using CoreData) or simply the checked IndexPaths. In -cellForRowAtIndexPath: you can then check if the cell is supposed to be checked.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *const identifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier];
cell.textLabel.textColor = UIColor.whiteColor;
}
if ([self.checkedIndexPaths containsObject:indexPath]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *const cell = [tableView cellForRowAtIndexPath:indexPath];
[table deselectRowAtIndexPath:indexPath animated:NO];
if ([self.checkedIndexPaths containsObject:indexPath]) {
[self.checkedIndexPaths removeObject:indexPath];
cell.accessoryType = UITableViewCellAccessoryNone;
}
else {
[self.checkedIndexPaths addObject:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
}
Since cells are being reused, you need to set the accessory mark to on or off for every cell in the table in the cellForRowAtInexPath table datasource method.
So the cell.accessoryType cell property should be soecified in the cellForRowAtInexPath and not the didSelectRow delegate method.
In the didSelectRow, just keep track of the selected rows in an array, and set the cells accessory mark to none or checkmark in the cellForRowAtInexPath dependingon the array value.