I am trying to save checked and remove unchecked tablecells into NSUserdefaults but it seems to ignore it as the NSLog in the for loop never gets called not sure why, is there something wrong with this code? (Everything shows up right and the cells get checked and un-checked correctly)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//setting up nsUser
userDefaults = [NSUserDefaults standardUserDefaults];
//current row text
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *cellName = cell.textLabel.text;
// Check if current row is selected
Boolean isNowChecked = NO;
if([tableView cellForRowAtIndexPath:indexPath].accessoryType == UITableViewCellAccessoryCheckmark)
{
isNowChecked = YES;
}
if(isNowChecked)
{
NSLog(#"UNCHECKING");
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
//takes away highlight from selection
[tableView deselectRowAtIndexPath:indexPath animated:YES];
//retrieve from nsdefaults
NSMutableArray *arrayOfCategories = [userDefaults objectForKey:#"categories"];
NSMutableArray *categoriesSelected;
//remove from nsuserdefaults
//add to nsuserdefaults
for (NSString* o in arrayOfCategories)
{
NSLog(#"%#",o);
if([o isEqualToString:cellName]) {
} else {
[categoriesSelected addObject:o];
}
}
//set for nsdefaults
[userDefaults setObject:categoriesSelected forKey:#"categories"];
[userDefaults synchronize];
}
else
{
NSLog(#"CHECKING");
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
//takes away highlight from selection
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSMutableArray *categoriesSelected;
//add to array
[categoriesSelected addObject:cellName];
//retrieve from nsdefaults
NSMutableArray *arrayOfCategories = [userDefaults objectForKey:#"categories"];
//add to nsuserdefaults
for (NSString* o in arrayOfCategories)
{
NSLog(#"%#",o);
[categoriesSelected addObject:o];
}
//set for nsdefaults
[userDefaults setObject:categoriesSelected forKey:#"categories"];
[userDefaults synchronize];
}
}
Change this line, otherwise you have an uninitialized Array:
NSMutableArray *categoriesSelected;
to:
NSMutableArray *categoriesSelected = [[NSMutableArray alloc] init];
Update: After looking at your code there are actually quite a few things that should be improved.
You should load the list of checked categories once in viewDidLoad and keep the list in an instance variable.
It's not a good idea to check to see if a cell is currently checked or not by looking at the cell's accessory type. Your data model should tell you which rows are checked.
You have a lot of duplicate code to toggle the checked state of a cell.
Use a set, not an array, to keep track of the checked cells.
Try the following changes:
Add an NSMutableSet instance variable to your class:
NSMutableSet *_checkedCategories;
In viewDidLoad, load the set:
NSArray *checkedCategories = [[NSUserDefault standardUserDefaults] objectForKey:#"categories"];
if (checkedCategories) {
_checkedCategories = [[NSMutableSet alloc] initWithArray:checkedCategories];
} else {
_checkedCategories = [[NSMutableSet alloc] init];
}
Now update your didSelectRow method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellName = ...; // get the cell name from your data model, not the cell
BOOL isChecked = [_checkedCategories containsObject:cellName];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (isChecked) {
[_checkedCategories removeObject:cellName]; // no longer checked
cell.accessoryType = UITableViewCellAccessoryNone;
} else {
[_checkedCategories addObject:cellName]; // now checked
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[[NSUserDefaults standardUserDefaults] setObject:[_checkedCategories allObjects] forKey:#"categories"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Related
I have an array of dictionaries in NSUserDefaults and those values are used to display data in a table view in another tab on the tab bar. It works ok if I add a new value to my NSUserDefaults array and close the app then go back into it and go the table view tab. The problem I'm having is when I add a new object to the user defaults array then go to the table view tab without leaving the app. When I do this, the table view does not show the just added values.
Here is where I add the dictionaries to NSUserDefaults:
- (void)addFavorite {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *favoritesLoaded = [defaults objectForKey:#"favorites"];
NSDictionary *fav = [NSDictionary dictionaryWithObjectsAndKeys:
self.postTitle, #"title",
self.postUrlString, #"url",
nil];
NSMutableArray *favorites = [NSMutableArray array];
if (favoritesLoaded) {
favorites = [[NSMutableArray alloc] initWithArray:favoritesLoaded];
} else {
favorites = [[NSMutableArray alloc] init];
}
[favorites insertObject:fav atIndex:0];
[defaults setObject:favorites forKey:#"favorites"];
[defaults synchronize];
}
In my table view:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = self.editButtonItem;
[self.tableView setDataSource:self];
[self.tableView setDelegate:self];
self.tableView.backgroundColor = [UIColor blackColor];
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"favorites"]) {
self.redditPosts = [[NSUserDefaults standardUserDefaults] objectForKey:#"favorites"];
}
else {
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
NSDictionary *post = [self.redditPosts objectAtIndex:[indexPath row]];
if (cell == nil) {
cell = [[CustomCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CustomCell"];
}
if (cell) {
cell.textLabel.text = [post objectForKey:#"title"];
cell.backgroundColor = [UIColor clearColor];
cell.textLabel.textColor = [UIColor whiteColor];
}
return cell;
}
just after inserting your new content you should reload the tableView:
[self.tableView reloadData];
if this can take a while you can use the following so it won't freeze-up your app:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
Following a previous SO question, I was trying to get my tableView cell selection right, with saving and other stuff.
I've making it toggle, but can't save the indexPath and retrieve it to display the selection even when the user leaves the view or restarts the app.
Here's the code:
#property (nonatomic, strong) NSIndexPath* lastIndexPath;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
if([self.lastIndexPath isEqual:indexPath])
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
...
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if(self.lastIndexPath)
{
UITableViewCell* uncheckCell = [tableView
cellForRowAtIndexPath:self.lastIndexPath];
uncheckCell.accessoryType = UITableViewCellAccessoryNone;
}
if([self.lastIndexPath isEqual:indexPath])
{
self.lastIndexPath = nil;
}
else
{
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
self.lastIndexPath = indexPath;
}
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSNumber numberWithInt:self.lastIndexPath.row] forKey:#"selectedCell"];
[defaults synchronize];
}
How can I save the indexPath and retrieve it to display the selection even after the user closes and reopens the view?
Thanks in advance.
You should add this in viewWillAppear.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSNumber *lastRow = [defaults objectForKey:#"selectedCell"];
if (lastRow) {
self.lastIndexPath = [NSIndexPath indexPathForRow:lastRow.integerValue inSection:0];
}
When you restart the app, you should get the lastIndexPath from NSUserDefaults, otherwise it is nil.
You should do two things:
1) In didSelectRowAtIndexPath, reload the previously selected row if it is visible
2) In cellForRowAtIndexPath, do a check in there if indexpath.row is the same as your selected cell. If it is, give it the check mark, if not then don't
This is how I am currently saving the state of the cell checkmark (which works):
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {\
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (cell.accessoryType == UITableViewCellAccessoryNone) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[defaults setBool:YES forKey:cell.textLabel.text];
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
[defaults setBool:NO forKey:cell.textLabel.text];
}
[defaults synchronize];
}
This is how I am attempting to retrieve them:
-(void)viewWillAppear:(BOOL)animated {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
for (int section = 0; section < [self.tableView numberOfSections]; section++) {
for (int row = 0; row < [self.tableView numberOfRowsInSection:section]; row++) {
NSIndexPath* cellPath = [NSIndexPath indexPathForRow:row inSection:section];
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:cellPath];
if (![defaults valueForKey:cell.textLabel.text]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
}
}
The NSUser defaults are saving correctly (I know this because of NSLog), but when I exit and return to the table view, no cells have checkmarks.
If I change the line:
if (![defaults valueForKey:cell.textLabel.text]) {
to
if ([defaults valueForKey:cell.textLabel.text]) {
the table view reappears with all cells having checkmarks.
Thanks
Your problem is that [self.tableView cellForRowAtIndexPath:cellPath]; can return nil if there is currently no cell for that row in the table - which is almost certainly the case in viewDidLoad because the view isn't visible yet. Even if the table has loaded that method can still return nil if the cell has scrolled out of view and the cell object has been released or reused.
You would be better storing your selection state in an NSMutableDictionary and storing the dictionary in NSUserDefaults. You can then check the contents of the dictionary in cellForRowAtIndexPath and set the accessory accordingly.
I've got a button pointing to a view and whenever I see press the button my app crashes. Can anyone tell me why? The code is the implementation file for the view that the button pushes to.
#implementation OpenShiftViewController
-(void)viewDidLoad
{
[super viewDidLoad];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
myDictionary = [defaults dictionaryRepresentation];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return myDictionary.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cell";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSArray * allKeys = [myDictionary allKeys];
cell.textLabel.text = [myDictionary objectForKey:[allKeys objectAtIndex:indexPath.row]];
cell.detailTextLabel.text = [myDictionary objectForKey:[allKeys objectAtIndex:indexPath.row]];
return cell;
}
#end
- (void)viewDidLoad
{
[super viewDidLoad];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
myDictionary = [defaults dictionaryRepresentation];
myMutableArray = [[NSMutableArray alloc]init];
[self storingObjectsFromDictionaryToArray];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)storingObjectsFromDictionaryToArray
{
NSArray *arr = [myDictionary allKeys];
[myMutableArray addObjectsFromArray:[myDictionary valueForKey:[arr objectAtIndex:0]]];
NSString *str = [NSString stringWithFormat:#"%#",[myDictionary valueForKey:[arr objectAtIndex:1]]];
[myMutableArray addObject:str];
str = [NSString stringWithFormat:#"%#",[myDictionary valueForKey:[arr objectAtIndex:2]]];
[myMutableArray addObject:str];
[myMutableArray addObjectsFromArray:[myDictionary valueForKey:[arr objectAtIndex:3]]];
[myMutableArray addObject:[myDictionary valueForKey:[arr objectAtIndex:4]]];
[myMutableArray addObjectsFromArray:[myDictionary valueForKey:[arr objectAtIndex:5]]];
[myMutableArray addObjectsFromArray:[myDictionary valueForKey:[arr objectAtIndex:6]]];
[myMutableArray addObject:[myDictionary valueForKey:[arr objectAtIndex:7]]];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return myMutableArray.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = [myMutableArray objectAtIndex:indexPath.row];
cell.detailTextLabel.text = [myMutableArray objectAtIndex:indexPath.row];
return cell;
}
The error is Thread 1: breakpoint 9.1
Ahm ... you might have a breakpoint activated ?
You are not getting an error - it is just cause of you having an activated breakpoint.
If you're using Xcode 4, then you can see 'Breakpoints' as seen below. Just click on that to disable breakpoints.
If you are using Xcode 5, then you have to click this Blue button to disable the breakpoint.
As AlexVogel pointed out in the comments, use init method to initialize the dictionary.
Alternatively, you can use lazy loading using property:
- (NSDictionary *)myLoadedDictionary{
if (!myDictionary) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
myDictionary = [defaults dictionaryRepresentation];
}
return myDictionary;
}
Now, in your class, consume [self myLoadedDictionary] instead of myDictionary.
I am not on my machine, but I am pretty much sure that you can define even myDictionary as a getter so that you can use that same name in your code and not myLoadedDictionary.
Like everyone else I would like to point out that should post your error.
Now I also see one problem with cellAtindexPath logic. Your trying to set data on null cell, see [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; will only give you something back if you've already given it something.
You've two options for loading a cell, the new way or the old way, both are kinda of same.
Option one and I recommend this one:
in your viewDidLoad (or what not) register a class for a cell identifer:
[self.tableview registerClass:[UITableViewCell class] forCellReuseIdentifier:#"cell"];
In CellForIndexPath to get the cell go:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
The forIndexPath is important here, using old api will return a nil cell.
Option 2:
Load a cell and if its nil, create a it.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"cell"];
}
I'm new to ios development, and I'm using checkmark for cells in a UITableView.
I want storing checked cell in a NSUserDefaults database, When I reload the app, the checked cell that previously selected will be display, I'm try to defferent ways, but still failed to implement it.
Anybody can help me? I would be highly appreciate it. Sorry, my english is not good.
My code is below:
#import "FirstRowSubview.h"
#interface FirstRowSubview ()
#end
#implementation FirstRowSubview
#synthesize array = _array;
#synthesize lastIndexPath = _lastIndexPath;
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *list = [[NSMutableArray alloc] initWithObjects:#"ffgfgh", #"564654", #"56548", #"fgmjfgmf", #"ggkdj", nil];
self.array = list;
[list release];
}
- (void)viewDidUnload
{
[super viewDidUnload];
self.array = nil;
}
#pragma mark -
#pragma mark - TableView Datasource Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [_array count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *subviewCells = #"Cells";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:subviewCells];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:subviewCells];
}
NSUInteger oldRow = [_lastIndexPath row];
cell.accessoryType = (indexPath.row == oldRow && _lastIndexPath != nil) ?
UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
cell.textLabel.text = [_array objectAtIndex:indexPath.row];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSNumber numberWithInt:_lastIndexPath.row] forKey:#"lastIndexPath"];
[defaults synchronize];
return cell;
}
#pragma mark -
#pragma mark - TableView Delegate Methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
int newRow = [indexPath row];
int oldRow = (_lastIndexPath != nil) ? [_lastIndexPath row] : -1;
if (newRow != oldRow) {
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:_lastIndexPath];
oldCell.accessoryType = UITableViewCellAccessoryNone;
[indexPath retain];
[_lastIndexPath release];
_lastIndexPath = indexPath;
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
}
It can be easy to store and retrieve checked cell(s) information with NSUserDefaults.
NSUserDefaults can store the data interms of Key / Value pair. Here in your case value could be boolean and key could combination of NSString & indexpath.row from UITableView.
You can make a functions like,
- (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];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [_array count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Here, you can check for previously checked cells like
static NSString *subviewCells = #"Cells";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:subviewCells];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:subviewCells];
}
cell.textLabel.text = [_array objectAtIndex:indexPath.row];
if([self getCheckedForIndex:indexPath.row]==YES)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:NO];
//Use checkedCellAtIndex for check or uncheck cell
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[self checkedCellAtIndex:indexPath.row];
if([self getCheckedForIndex:indexPath.row]==YES)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
P.S. Please, note that, this method will only useful and suggested if order of data from your NSArray will always in same order. Other then NSUserDefaults you've options like SQLite Database or plist file or any other option! Thanks :)
This is a basic example of how to create an array of NSIndexPaths based on the current selection of the table and then saving that array into NSUserDefaults.
-(void)viewDidLoad
{
[super viewDidLoad];
for (NSIndexPath *indexPath in [[NSUserDefaults standardUserDefaults] mutableArrayValueForKey:#"mySavedMutableArray"]) {
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}
Here you can add objects to the array as they are selected
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[[[NSUserDefaults standardUserDefaults] mutableArrayValueForKey:#"mySavedMutableArray"] addObject:indexPath];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Here you'll remove the deselected object from the array.
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
[[[NSUserDefaults standardUserDefaults] mutableArrayValueForKey:#"mySavedMutableArray"] removeObject:indexPath];
[[NSUserDefaults standardUserDefaults] synchronize];
}
The simplest way to get your code working is just put one line in view did load method as follow:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if([defaults valueForKey:#"lastIndexPath"])
_lastIndexPath = [defaults valueForKey:#"lastIndexPath"];
This will solve your problem :)
Happy Coding :)