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.
Related
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)
The following code is returning an exception with the following error message "mutating method sent to immutable object" when attempting to removeObjectForKey
NSMutableDictionary * storedIpDictionary = (NSMutableDictionary*)[[NSUserDefaults standardUserDefaults] dictionaryForKey:#"dictDeviceIp"];
NSString *key = self.currentDeviceNameText.text;
NSString *ipAddressTemp = [storedIpDictionary objectForKey:key];
[storedIpDictionary removeObjectForKey:key]; <----Crashes here
storedIpDictionary[key] = ipAddressTemp;
Not sure what the issue is, perhaps it is due to retrieving the dictionary from a NSUserDefaults.
However the following code works without any issues.
NSMutableDictionary * storedIpDictionary = (NSMutableDictionary*)[[NSUserDefaults standardUserDefaults] dictionaryForKey:#"dictDeviceIp"];
[storedIpDictionary removeAllObjects];
NSUserDefaults returns immutable objects, even if you put in mutable ones. You must call -mutableCopy on the returned value to get a mutable collection.
You cant just cast an NSDictionary to NSMutableDictinary thats not at all how casting works.
to remove a key from NSUserDefualts call removeObjectForKey on the NSUserDefaults instance itself.
if you really do want a dictionary for some other reason, then you must make a mutableCopy from the dictionary obtained by dictionaryForKey.
This is the code that eventually worked, I used some of the details provided from others above, but none had it completely explained.
- (void)cleanDictionary
{
NSMutableDictionary * storedIpDictionary = [[[NSUserDefaults standardUserDefaults] objectForKey: #"dictDeviceIp"] mutableCopy];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"dictDeviceIp"];
NSString *oldKey = self.currentDeviceNameText.text;
NSString *newKey = self.deviceNameChangeText.text;
NSString *ipAddressTemp = [storedIpDictionary objectForKey:oldKey];
// Make some change to the structure
[storedIpDictionary removeObjectForKey:oldKey]; // Remove object
storedIpDictionary[newKey] = ipAddressTemp; // Add object with new key
// Add it the whole thing back into NSUserDefaults
[[NSUserDefaults standardUserDefaults] setObject:storedIpDictionary forKey:#"dictDeviceIp"];
// Synchronize to ensure it's saved
[[NSUserDefaults standardUserDefaults] synchronize];
}
if you have on error NSMutableDictionary: mutating method sent to immutable object in Swift, make this step:
This is because you have assigned a NSUserDefault to NSMutableArray, when you take something NSUserDefault it returns you a NSArray not a NSMutableArray, so in this case you have to use a NSMutableArray Auxiliary .
see for Swift :
var Products:NSMutableArray = NSMutableArray()
override func viewDidAppear(animated: Bool) {
if let Produtos = NSUserDefaults.standardUserDefaults().valueForKey("Produtos") {
Products = Produtos as! NSMutableArray
}
}
func InsertProducts(productCode:String){
//COPY Products Atual for auxMutable
var auxMutable = Products.mutableCopy()
//Add object in auxMutable
auxMutable.addObjectsFromArray([productCode])
//in line back data to Array Products and make cast to NSMutableArray
Products = auxMutable as! NSMutableArray
//Refresh Data of NSUserDefaults
NSUserDefaults.standardUserDefaults().setObject(Products, forKey: "Produtos")
}
#IBAction func Bt_New_Product(sender: AnyObject) {
var ProductName:String = TXT_NameProduct.text
InsertProducts(ProductName)
}
This work for me!!!
i found same issue and found solution hope it will help some one.
arrayOfferId = defaults.objectForKey("offerId")?.mutableCopy() as! NSMutableArray
NSUserDefaults returns immutable objects, even if you put in mutable ones. You must call -mutableCopy on the returned value to get a mutable collection. so when you get value from NSUserDefault use mutableCopy()
[NSUserDefaults dictionaryForKey] returns an immutable dictionary (NSDictionary) and you cannot force it to be mutable by casting it to NSMutableDictionary.
Instead you must create the mutable dictionary using mutableCopy, overwrite the element and then re-assigning the dictionary back into NSUserDefaults:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *storedIpDictionary = [[userDefaults dictionaryForKey:#"dictDeviceIp"] mutableCopy];
NSString *key = self.currentDeviceNameText.text;
NSString *ipAddressTemp = [storedIpDictionary objectForKey:key];
// Don't need this line
//[storedIpDictionary removeObjectForKey:key];
storedIpDictionary[key] = ipAddressTemp;
[userDefaults setObject:storedIpDictionary
forKey:#"dictDeviceIp"];
I have an array inside a NSMutableDictionary and i want to add objects to it. With my current approach I get an error saying that the array is immutable.
I think the problem lies when I´m saving the dictionary to NSUserDefaults. I´m retrieving the is it a NSDictionary but I am at the same time creating a new NSMutableDictionary with the contents.
However, the array seems to be immutable. How do I replace an array inside of a dictionary?
My dictionary looks like this:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:[NSNumber numberWithInt:0], nil];
NSDictionary *dict = #{
#"firstKey": #{
#"theArray":array,
}
};
NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
I am trying to add objects like this:
[[[mutDict objectForKey:#"firstKey"] objectForKey:#"theArray"] addObject:[NSNumber numberWithInt:5]];
I am able to add objects to the array inside mutDict before its saved to NSUserDefaults
The error message I get when I try to add to the array inside the dictionary after loading it from NSUserDefaults:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object'
Here's what the documentation for dictionaryForKey: says on NSUserDefaults:
Special Considerations
The returned dictionary and its contents are immutable, even if the values you >originally set were mutable.
So when you retrieve your dictionary from NSUserDefaults the dictionary itself and all of the collections inside it are immutable. You can make the top level dictionary mutable (which I assume you are doing), but that won't propagate down into the now immutable NSArrays which are values in the dictionary.
The only way to get around this is to go through the dictionary that's returned and replace the immutable NSArrays with their mutable counterparts. It might look something like this.
- (NSMutableDictionary *)deepMutableCopyOfDictionary:(NSDictionary *)dictionary
{
NSMutableDictionary *mutableDictionary = [dictionary mutableCopy];
for (id key in [mutableDictionary allKeys]) {
id value = mutableDictionary[key];
if ([value isKindOfClass:[NSDictionary class]]) {
// If the value is a dictionary make it mutable and call recursively
mutableDictionary[key] = [self deepMutableCopyOfDictionary:dictionary[key]];
}
else if ([value isKindOfClass:[NSArray class]]) {
// If the value is an array, make it mutable
mutableDictionary[key] = [(NSArray *)value mutableCopy];
}
}
return mutableDictionary;
}
To be honest though it sounds like you're using NSUserDefaults for something a lot more complex then it is intended for. If you want to persist complex data structures then you should look into something like Core Data, or if that looks to be a bit overkill take a look at NSKeyedArchiver.
You can add object directly to the array:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:[NSNumber numberWithInt:0], nil];
NSDictionary *dict = #{
#"firstKey": #{
#"theArray":array,
}
};
NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
//Since Objective-C objects are always passed by reference (using pointers) you can add object to the array
[array addObject:[NSNumber numberWithInt:55]];
Swift example of adding object to array which is part of a dictionary.
let arr = [0] // note that initial array may be immutable
var dict = ["fK": ["a":arr]] // even if "arr" will be mutable, but "dict" immutable
dict["fK"]!["a"]!.append(3) // this will not work. "dict" must be mutable
println(dict) //[fK: [a: [0, 3]]]
Another approach
var arr = [0] // initial array must be mutable
var dict = ["fK": ["a":arr]] // in both cases dictionary must be mutable
arr.append(3)
let newArr = arr
dict["fK"]!["a"]! = newArr // because we change it's content
println(dict) //[fK: [a: [0, 3]]]
I am using below function to check whether if an object in an array is present in another array. If the object not present, then I will ADD that object to the new array, or else that object will NOT be included in the new array that I instantiated.
+ (NSMutableArray *)loadUngroupedSpeakerList
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *speakerList = [[NSMutableArray alloc] init];
NSArray *speakerIDList = [userDefaults objectForKey:DATA_SPEAKER_IDLIST];
NSArray *speakerIDListGrouped = [userDefaults objectForKey:DATA_SPEAKER_IDLIST_GROUPED];
//**** this is for checking the contents of speakerIDListGrouped ****//
for(NSString *speakerID in speakerIDListGrouped)
{
NSLog(#"FLOWCHECK~ loadUngroupedSpeakerList check content:%#", speakerID);
}
for(NSString *speakerID in speakerIDList)
{
if(![speakerIDListGrouped containsObject:speakerID])
{
NSLog(#"FLOWCHECK~ loadUngroupedSpeakerList: speakerID: %#", speakerID);
NSDictionary *speakerDict = [userDefaults dictionaryForKey:[NSString stringWithFormat:#"%#%#", DATA_SPEAKER_DICT, speakerID]];
[speakerList addObject:speakerDict];
}
}
return speakerList;
}
In the above code, speakerList contains all the speakerIDs. While speakerIDListGrouped only contains the speakerIDs that are used in a group. My function needs to eliminate all the speakerIDs used in a group so I did it in a way just like the above code.
My Problem:
When I run the code, I notice that even if speakerIDListGrouped contains the object in speakerIDList, these two lines would still be executed
NSDictionary *speakerDict = [userDefaults dictionaryForKey:[NSString stringWithFormat:#"%#%#", DATA_SPEAKER_DICT, speakerID]];
[speakerList addObject:speakerDict];
Whereas to I understand, It should not happen. Because I only allowed them to be executed only if speakerIDList does not contain that object.
This is the log when I execute the code:
2015-06-15 19:31:24.849 soulbeats[1936:433953] FLOWCHECK~ loadUngroupedSpeakerList check content:72243140485836704
2015-06-15 19:31:24.850 soulbeats[1936:433953] FLOWCHECK~ loadUngroupedSpeakerList check content:7782687177520836128
2015-06-15 19:31:24.850 soulbeats[1936:433953] FLOWCHECK~ loadUngroupedSpeakerList: speakerID: 72243140485836704
2015-06-15 19:31:24.851 soulbeats[1936:433953] FLOWCHECK~ loadUngroupedSpeakerList: speakerID: 7782687177520836128
As can be seen, speakerIDListGrouped DOES contain the two objects. However, when I tried replacing the string inside the lower for loop by hardcoding it to one of the objects I printed on Log, which was 72243140485836704. The function now works properly, I mean it didn't execute the two lines I showed before.
I am now confused. What is the difference between the string I hardcoded and the one that was obtained from the array? The contents are the same.
Many Thanks!
I did the same thing it is working fine...
NSMutableArray *speakerList = [[NSMutableArray alloc] init];
NSArray *speakerIDList = #[#"a",#"b",#"c",#"d",#"e"];
NSArray *speakerIDListGrouped =#[#"a",#"b",#"f",#"g",#"h"];
for(NSString *speakerID in speakerIDListGrouped)
{
NSLog(#"%#", speakerID);
}
for(NSString *speakerID in speakerIDList)
{
if(![speakerIDListGrouped containsObject:speakerID])
{
NSLog(#"FLOWCHECK~ loadUngroupedSpeakerList: speakerID: %#", speakerID);
[speakerList addObject:speakerID];
}
}
There might be some issue with the objects inside the array....
This answer will help other's. It's very simple, use following method of NSArray
id commonObject = [array1 firstObjectCommonWithArray:array2];
Ref: https://developer.apple.com/documentation/foundation/nsarray/1408825-firstobjectcommonwitharray?language=objc
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.