In my app I am storing a list of sets of data in NSUserDefaults. When it needs to be accessed, I create create an array (each key in NSUserDefaults contains and array of data) and use the data. It works great. Yay! Unfortunately, when I display the data in a UITableView, deleting one set of data ( which == one row in the UITableView ) is not working properly. This is how I am going about doing that:
if(buttonIndex == actionSheet.destructiveButtonIndex)
{
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
int iii = [defaults integerForKey:#"currentMap"];
NSMutableDictionary* dicOne = (NSMutableDictionary*)[[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
for(int lll = iii; lll < [[defaults objectForKey:#"counterKey"] intValue]; lll++)
{
if(lll != [[defaults objectForKey:#"counterKey"] intValue] - 1)
[dicOne setObject:[dicOne objectForKey:[NSString stringWithFormat:#"%i", (iii + 1)]] forKey:[NSString stringWithFormat:#"%i", iii]];
else
[dicOne removeObjectForKey:[NSString stringWithFormat:#"%i", iii]];
}
[defaults setPersistentDomain:dicOne forName:[[NSBundle mainBundle] bundleIdentifier]];
[defaults synchronize];
int counter = [defaults integerForKey:#"counterKey"];
counter--;
[defaults setInteger:counter forKey:#"counterKey"];
[defaults synchronize];
[self performSelector:#selector(done:) withObject:nil afterDelay:.3];
}
The idea behind this code is to start a for loop at the index of the selected row to be deleted ( variable 'iii'). This method moves through the dictionary, moving each index down a level.
Imagine the dictionary containing five sets of data (five keys), labeled 0, 1, 2, 3, and 4. When 2 gets marked for deletion, this code moves through the dictionary, assigning the data contained in key 3 to key 2, the data in key 4 to key 3, then deletes the remaining copy in key three. The counterKey then gets decremented (as this is the number I use to keep track of how many keys exist, so I can tell the UITableView how many cells it needs to create). Or at least that is what I think it should do.
But it doesn't do what I think it should do; when a cell gets marked for deletion, what really happens is that the data for that key shows up as NULL, and the keys do not 'slide' like I think they should.
The done: method dismisses the information view (that contains the delete button and information about the selected row) and returns to the view that holds the UITableView.
Does this concept make sense, and if so, why doesn't it work? Thanks for your time.
***Edit:
Thank you, jrturton, using an NSArray worked, but sometimes deleting it (or trying) crashes with this error:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFArray removeObjectAtIndex:]: mutating method sent to immutable object'
* First throw call stack:
(0x14c7052 0x1658d0a 0x146fa78 0x146f9e9 0x14c109f 0xd381 0x8cba1f 0x14c8ec9 0x5105c2 0x51055a 0x5b5b76 0x5b603f 0x5b52fe 0x535a30 0x535c56 0x51c384 0x50faa9 0x24adfa9 0x149b1c5 0x1400022 0x13fe90a 0x13fddb4 0x13fdccb 0x24ac879 0x24ac93e 0x50da9b 0x1ef9 0x1e75)
Which is odd, because there is not a single NSArray used in that class; all of them are mutable.
This is the line of the crash (I am reasonably sure):
NSMutableArray* outerArray = (NSMutableArray*)[defaults objectForKey:#"mapSaveDataKey"];
[outerArray removeObjectAtIndex:iii];
Fix:
Replace:
[defaults objectForKey:#"mapSaveDataKey"];
With:
[defaults mutableArrayValueForKey:#"mapSaveDataKey"];
What you are doing seems unnecessarily complicated. Why not just have a single array stored in your user defaults, under a key (e.g #"data"), then remove or add items to that array? This way you don't have to maintain separate counts or anything like that. You can use objectAtIndex: to get hold of item 4, for example, and when you delete item 2, the items above it will move down a notch anyway.
Update - as Matthew Gillingham points out in the comments, I did mean a mutable array. As youve discovered, you can't alter the number or position of elements in an NSArray.
Note also that arrayForKey: will return an NSArray even if you stored a mutable array in the first place, so when getting the value from defaults you will have to do the following:
NSMutableArray *myData = [NSMutableArray arrayWithArray:[defaults arrayForKey:#"data"]];
Where defaults is your user defaults object.
Related
As I am storing one Integer value into NSUserDefaults aas its used at many places. At first time its working fine but while i close my application and again open it I am check that user already selected any option in past feom NSUserDefaults stored value but I am failed in that
Some thing is wrong in my case
Here is my code for checking Integer value is there in userdefault :
NSInteger selectedBusinessUnit = [[NSUserDefaults standardUserDefaults] integerForKey:#"selectedUnit"];
if ( selectedBusinessUnit != NSNotFound){
//go to direct main screen.
}else {
// load Business unit screen for selection.
}
But its always found value even i am deleting app and reinastall it.
Always my selected value is 0.
Let me know what is my silly mistake here.
Edit:
[[NSUserDefaults standardUserDefaults] setInteger:sender.tag forKey:#"selectedUnit"];
[[NSUserDefaults standardUserDefaults] synchronize];
THNAKS .
Obviously there is a misunderstanding: NSNotFound is not equal to key is missing, it's a valid integer value.
The easiest way to keep your logic is to register the key-value pair with NSNotFound as the default value.
As soon as possible (applicationDidFinishLaunching or earlier) write
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *defaultValues = #{#"selectedUnit": #(NSNotFound)};
[defaults registerDefaults:defaultValues];
That means NSNotFound is considered as default value until it's overwritten the first time. The 3 lines must be executed every time the application launches. If the app is reinstalled the default value is taken again.
Now you can use your logic in the question.
PS: You don't need to synchronize after writing. The framework does that periodically.
The default value for integer is 0, according to the documentation:
-integerForKey: is equivalent to -objectForKey:, except that it converts the returned value to an NSInteger. If the value is an
NSNumber, the result of -integerValue will be returned. If the value
is an NSString, it will be converted to NSInteger if possible. If the
value is a boolean, it will be converted to either 1 for YES or 0 for
NO. If the value is absent or can't be converted to an integer, 0 will
be returned.
if you want to check if a key exists in NSUserDefaults use:
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"selectedUnit"] != nil)
{
...
}
NSUserDefaults storage only object, NSNumbers.
I think the problem in conversion from NSNumber, which you stored to integer in method integerForKey.
Another case - may be you forgot a synchronize NSUserDefaults.
You can set object like
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:#(yourIntegerValue) forKey:#"yourIntegerKey"];
[userDefaults synchronize];
and you can check like this
NSNumber *integerValue = [userDefaults objectForKey:#"yourIntegerKey"];
if (integerValue) {
//
}
I have two Viewcontrollers in FirstVC i build 5 UITextField for registration ,this TextField value are stroed in dictionary finally the dictionary stored in NSUserdefault then in SecondVC i want to show this data
My problem is that each time when i add new discretionary in NSUserdefault The old one dictionary was replaced
i want data of all dictionary.
below is code of my FirstVC
-(void)btnReg
{
//data add in disctionary
for (int i=1; i<=5; i++)
{
UITextField *txtTemp=(UITextField *)[self.view viewWithTag:i];
[discRege setObject:[NSNumber numberWithInt:count] forKey:#"no"];
[discRege setObject:txtTemp.text forKey:[arraylblName objectAtIndex:i-1]];
}
//dictionary add in nsuserdefault
[[NSUserDefaults standardUserDefaults]setObject:discRege forKey:#"ABC"];
[[NSUserDefaults standardUserDefaults]synchronize];
//push to SecondVc
secondViewController *objSec=[[secondViewController alloc]init];
[self.navigationController pushViewController:objSec animated:YES];
[self.navigationController setNavigationBarHidden:false];
}
below is code of my SecondVC
ArratTemp =[[NSUserDefaults standardUserDefaults]objectForKey:#"ABC"] ;
if (!ArratTemp )
{
ArratTemp =[[NSMutableArray alloc]init];
}
else
{
ArratTemp = [[[NSUserDefaults standardUserDefaults]objectForKey:#"ABC"]mutableCopy];
}
NSLog(#"%#",ArratTemp);
Every time you are using the same key and replacing the existing dictionary object...
// Using the same key will overwrite the last saved dictionary.
[[NSUserDefaults standardUserDefaults] setObject:discRege forKey:#"ABC"];
Instead of storing it as a dictionary, store it as an array of dictionaries. Whenever you add new registration, fetch the saved array, add new dictionary object into it and update the userDefaults with that array.
mutableArray = [[[NSUserDefaults standardUserDefaults]objectForKey:#"ABC"] mutableCopy];
[mutableArray addObject:discReg];
[[NSUserDefaults standardUserDefaults] setObject:mutableArraay forKey:#"ABC"];
[[NSUserDefaults standardUserDefaults] synchronize];
Hope it helps.
You're overwriting the same disctionary every time.
You have two solutions :
Solution 1.
Store different dictionaries under different keys, not all under "ABC". So in your for loop can use the index (i) to have multiple entries, instead of just ABC every time. Here it's a simple matter you can resolve yourself. Make sure to not store everything under the same Key, and you'll find them ;) For example, you could save under [NSNumber numberWithInt:i]and then browse your NSUserDefaults for 0, 1, 2, 3... and so on. I recommend against this btw, solution 2 is the way to go.
Solution 2.
Store all your dictionaries in an array, and then store the array in the NSUserDefaults.
For that, simply create an NSMutableArray that you keep empty, then add dictionaries in it !
NSMutableArray dataArray = [[NSMutableArray alloc]init];
for (int i=1; i<=5; i++)
{
//Creating new dictionary
NSMutableDictionary *currentDict = [[NSMutableDictionary alloc]init];
//Getting the text we want
UITextField *txtTemp =(UITextField *)[self.view viewWithTag:i];
NSString *text = txtTemp.text;
//This is here because you had it
[currentDict setObject:[NSNumber numberWithInt:count] forKey:#"no"];
//All dictionaries will have key = name of the Label,
//but you could change it to something static, like
// "Content" for example. It'll be easier to find later
[currentDict setObject:text forKey:[arraylblName objectAtIndex:i-1]];
//Adding that newly formed dictionary to the mutable array.
[dataArray addObject:currentDict];
}
//Adding the array containing dictionaries to the NSUSerDefaults
[[NSUserDefaults standardUserDefaults]setObject:dataArray forKey:#"ABC"];
Note : I'm not exactly sure what you're doing with the dictionaries in the for loop, but since you didn't show the code, I'm guessing its' not part of the question. With my answer you have enough information to make some corrections if needed. All you need to remember is :
Create a dictionary per answer, and not one for all
Put each dictionary in the same array
Save the array (containing all the dictionaries)
When the following code executes, it logs "0" even though the indexPath.row selected is "1".
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSInteger location = indexPath.row;
[userDefaults setInteger:location forKey:#"savedlocations"];
NSInteger location2 = [userDefaults integerForKey:#"savedlocation"];
NSLog(#"l: %ld", location);
You've got several things that need to be fixed:
You're saving with the key savedlocations, but retrieving it with savedlocation. Note that the first is plural.
You're logging location, not location2, which is what's being pulled from NSUserDefaults. location2 will always be 0, because of the point above.
Edit: I had a third point here about calling synchronize, but it turns out that that is irrelevant to this situation, as explained by rmaddy in the comments below.
Other than those quick fixes, though, your configuration ought to work.
You are looking for the key "savedlocation" instead of "savedlocations".
EDIT:
0 is being returned by the method, which is what is returned for an NSInteger that can't be found. My bad on the weird wording.
My app allows users to create and several several arrays, from the media picker, saving each with a different user selected name. How can I allow the user to make a duplicate of one of the arrays and save under a different file name?
example list shows user created arrays
array1
array2
array3
Now the user wants to create a new array just like array3 but they will delete a few items rather than create an entire new array.
So I want the user to be able to make a copy of array3 naming it array4 and then make a be able to a few changes to array4 and save time.
Hope that makes some sense.
After the media picker selection of songs, this is my save playlist method:
- (void)savePlaylist:(MPMediaItemCollection *) mediaItemCollection
{
NSArray* items = [mediaItemCollection items];
if (items == nil)
{
return;
}
NSMutableArray* listToSave = [[NSMutableArray alloc] initWithCapacity:0];
for (MPMediaItem *song in items)
{
NSNumber *persistentId = [song valueForProperty:MPMediaItemPropertyPersistentID];
[listToSave addObject:persistentId];
}
//read playlist title
NSUserDefaults *defaults;
defaults = [NSUserDefaults standardUserDefaults];
NSString *thissongsList;
thissongsList = [defaults objectForKey:#"savetextkey"];
//save playlist
defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject: thissongsList forKey:#"savetextkey"];
[defaults synchronize];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject: listToSave];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:_songsList];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Asking to duplicate an array is fine, but we don't even know what language you're using. If you are using Objective-C, then all you have todo in order to copy an array is send the "copy" command on an array. Example below.
NSArray *myArray = [oldArray copy];
This is called a "shallow copy" which means it'll only copy the pointer references and not the actual objects. If you need to make brand new instances of the objects, then you'll need to adopt the NSCopying protocol in your objects, and override the copyWithZone method.
If you are using Swift then you'll basically do the same thing. To copy the array you'll use the code below.
var myArray = oldArray
Then use the same NSCopying protocol if you want to make a deep copy.
That's it, that is all you've got todo to copy an array.
Thanks
can you help me? I´m searching here for a while and testet many things... no solution found!
I have set a NSMutableArray in the .h File:
#interface ViewController : UIViewController
{
NSMutableArray *Transactions;
}
In the .m file in the ViewDidLoad Method I initialized it and load the Array from the UserDefaults:
Transactions = [NSMutableArray array];
or
Transactions = [NSMutableArray new];
Transactions = [defaults objectForKey:#"transactions"];
Later i add a new Dictionary to it:
[insert setObject:confirmPaymentStatus forKey:#"status"];
[insert setObject:confirmPaymentAmount forKey:#"amount"];
[insert setObject:confirmPaymentDiscription forKey:#"description"];
[insert setObject:timestamp forKey:#"time"];
NSLog(#"%#",insert);
[Transactions addObject:insert];
NSLog(#"Transactions Array:\n%#",Transactions);
[defaults setObject:Transactions forKey:#"transactions"];
[defaults synchronize];
The insert Dictonary is full of data and then i got this from the Log:
2014-02-05 20:56:53.691 PPEasyPay Pro Pro[21907:60b] {
amount = "0.91";
description = "Polaris 123123";
status = COMPLETED;
time = "2014-02-05T11:56:51.248-08:00";
}
2014-02-05 20:56:53.692 PPEasyPay Pro Pro[21907:60b] Transactions Array:
(null)
It sounds like [defaults objectForKey:#"transactions"] is returning null, and therefore you're assigning your array to null. From there, you add an object to your non existent array, and since the array doesn't exist, nothing actually happens. Instead, you should create your array, and only add the objects from the other array to it if they exist.
NSArray *newStuff = [defaults objectForKey:#"transactions"];
transactions = [NSMutableArray new];
if (newStuff) {
[transactions addObjectsFromArray:newStuff];
}
Side note, your instances should be camelCase starting with a lowercase letter.
Transactions = [NSMutableArray new];
Transactions = [defaults objectForKey:#"transactions"];
When you do that, you are creating a new NSMutableArray, then that array is being discarded when you assign the result of [defaults objectForKey:#"transactions"] to the same variable.
I'd be willing to guess that the result of [defaults objectForKey:#"transactions"] is nil, which is why your array is nil.
Also, by convention, the Transactions variable should begin with a lowercase letter, or even an underscore followed by a lowercase letter.
The problem is that Transactions itself was nil to start with. You then say:
[Transactions addObject:insert];
But this has no effect: sending addObject: to nil still leaves it as nil.
What you need to do is this: after you fetch Transactions from the defaults, look to see if it is nil. If it is, set it to an empty mutable array instead.
Also, please note that this code is utterly stupid:
Transactions = [NSMutableArray array];
Transactions = [defaults objectForKey:#"transactions"];
You set Transactions to an empty mutable array, but then you immediately throw away that empty mutable array and replace it by whatever comes from the defaults (i.e. nil).
Transactions = [NSMutableArray array];
or
Transactions = [NSMutableArray new];
Transactions = [defaults objectForKey:#"transactions"];
You have several problems here. The first is that what you've written is the object equivalent of this:
int i;
i = 5;
i = 3;
What's i? Well, 3 of course. What does the i = 5 line do? Absolutely nothing. (Except possibly take CPU time, depending on optimization.)
The second problem is that [defaults objectForKey:] is going to return an immutable object.
From Apple's docs for objectForKey::
Special Considerations
The returned object is immutable, even if the value you originally set was mutable.
The reason this works without error is that objectForKey: returns an id; you can assign an id to any object variable without an error. But this is like a C pointer cast, not a type coercion. You haven't made the return value a NSMutableArray, you've just shown the compiler you don't care what it is.
What you want is this:
NSArray *readOnlyTransactions = [defaults objectForKey:#"transactions"];
if (readOnlyTransactions == nil) {
readOnlyTransactions = #[];
}
_transactions = [readOnlyTransactions mutableCopy];
You'll notice I renamed your instance variable to _transactions. This is the convention for instance variables, and you should probably adapt to it.
You can do this more succinctly with the ?: operator, if you're comfortable with using it:
_transactions = [([defaults objectForKey:#"transactions"] ?: #[]) mutableCopy];
What this does:
Read [defaults objectForKey:#"transactions"].
If that returns nil, use an empty list instead.
Make a mutable copy of it.
Assign the thing to _transactions.