NSUserDefaults won't save NSDictionary - iphone

I'm writing an application which uses NSUserDefaults as the data storage mechanism, and am hitting a problem when trying to save data (that conforms to the Property List protocols):
+ (BOOL)storeAlbum:(Album *)album
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *albums = (NSMutableDictionary *)[prefs objectForKey:#"my_adventure_book_albums"];
NSLog(#"Existing albums: %#",albums);
if (!albums)
albums = [NSMutableDictionary dictionaryWithObject:album forKey:#"album"];
else
[albums setObject:album forKey:#"album"];
NSLog(#"%#",album);
[prefs setObject:albums forKey:#"my_adventure_book_albums"];
return [prefs synchronize];
}
I get this output:
2010-06-29 17:17:09.929 MyAdventureBook[39892:207] Existing albums: (null)
2010-06-29 17:17:09.930 MyAdventureBook[39892:207] test
2010-06-29 17:17:09.931 MyAdventureBook[39892:207] *** -[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '{
album = test;
}' of class 'NSCFDictionary'.
The description method of Album looks like:
- (NSString *)description
{
// Convert to a NSDictionary for serializing
if (!title) title = #"";
if (!date) date = [NSDate dateWithTimeIntervalSinceNow:0];
if (!coverImage) coverImage = #"";
if (!images) images = [[NSArray alloc] initWithObjects:#"",nil];
//NSDictionary *dict = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:title,date,coverImage,images,nil] forKeys:[NSArray arrayWithObjects:#"title",#"date",#"coverImage",#"images",nil]];
//NSDictionary *dict = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:title,nil] forKeys:[NSArray arrayWithObjects:#"title",nil]];
//return [dict description];
return #"test";
}
All of the commented-out lines have the same result, so I just decided to see if the NSString "test" would work, which it (of course) doesn't.

But the object you put inside the dictionary, an Album* is most likely not a property list object, is it? Every object, all the way down, needs to be a property list object for this to work. A description method isn't good enough to make this happen.
As a workaround, you can use NSCoding and an NSKeyedArchiver to write out your dictionary to an NSData, which you can store among the preferences.

You can only put basic foundation types into a property list. NSUserDefaults writes preferences out as a property list. See here for property list allowed types. In a nutshell, it is numbers, strings, data, dates, and arrays and dictionaries of those. Dictionaries must have string keys.

NSUserDefaults always returns immutable objects, so you can't just cast them to mutable. Do [prefs objectForKey:#"my_adventure_book_albums"] mutableCopy] (and remember to release it when finished).

Related

How can I pass a "MutableArray with full of Objects" to another class by using NSUserDefaults?

How can I pass a "MutableArray with full of Objects" to another class by using NSUserDefaults? I know how to pass "MutableArray"s but this does not work!
So;
I have a MutableArray; 'myCityObjects', and I populate it with objects; 'cities'
In each 'cities' object there are properties like cityName, cityLocation etc...
[myCityObjects addObject:cities];
Now, what I want to do is to pass this MutableArray (filled with objects) to another class by using 'NSUserDefaults';
[[NSUserDefaults standardUserDefaults] setObject: myCityObjects forKey:#"MCO"];
And in the other class,
NSMutableArray *getMyCityObjects = [[NSArray alloc] init];
getMyCityObjects = [[NSUserDefaults standardUserDefaults] mutableArrayValueForKey:#"MCO"];
But it doesn't work! I cannot get myCityObjects in the other class, "getMyCityObjects" is empty. How can I fix that?
Thanks,
E.
NSUserDefaults always returns immutable objects, even if the original object was mutable.
In your first View, You can save value in NSUserDefaults like this:
NSMutableArray *arr= [NSMutableArray arrayWithObjects:#"asd",#"dsa",nil];
[[NSUserDefaults standardUserDefaults] setObject:arr forKey:#"MCO"];
After this in another view, you can retrieve value from NSUserDefaults in this way.
NSMutableArray *abc = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:#"MCO"]];
Your array is nil because the objects in it (your custom objects) can't be serialised.
Please take a look at the NSCoding protocol. Objects you want to serialise (eg for writing to NSUserDefaults) must implement the methods -encodeWithCoder: and -initWithCoder.
I'm sure you'll find how this is rather easily done searching for the terms I gave you...
I have run into this problem before. The problem with the NSUserDefaults is that it can only contain strings, numbers, and booleans, and arrays or dictionaries of those types of values. My solution is to get around that by storing all the properties in NSDictionaries.
Create two class functions on your "cities" class (I'm calling it CityClass):
+(NSDictionary *)dictionaryFromCity:(CityClass *)myCity {
NSDictionary *returnDict = #{#"keyForIntProperty" : myCity.intProperty, #"keyForFloatProperty" : myCity.floatProperty, #"keyForNSStringProperty", myCity.NSStringProperty"};
return returnDict;
}
+(CityClass *)cityFromDictionary:(NSDictionary *)myDict {
CityClass *returnCity = [[CityClass alloc] init];
returnCity.intProperty = [[myDict objectForKey:#"keyForIntProperty"] intValue];
returnCity.floatProperty = [[myDict objectForKey:#"keyForFloatProperty"] floatValue];
returnCity.NSStringProperty = [myDict objectForKey:#"keyForNSStringProperty"];
//any other setup for the CityClass
return returnCity;
}
Now you can store and retrieve your objects without a problem using the new functions:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//store a CityClass object
NSDictionary *storageDict = [CityClass dictionaryFromCity:cityToStore];
[defaults setObject:storageDict forKey:#"keyForCity"];
//retrieve a CityClass object
NSDictionary *retrieveDict = [defaults objectForKey:#"keyForCity"];
CityClass *retrievedCity = [CityClass cityFromDictionary:retrieveDict];
What you can do here is create a Constructor in your other class for e.g.
-(void)initWithArrayOfObject:(NSMutableArray *)arr_OfObjects;
{
arr_SecondClassArrayOfObjects = [[NSMutableArray alloc]initWithArray:arr_OfObjects];
}
From your first class send this array as :
[objOfSecondClass initWithArrayOfObject: myCityObjects];

NSUserDefaults NSObject with NSArray Object

I am trying to save object in NSUserDefaults, went throught many questions on this site but could not resolve the issue, my NSObject has an NSMutableArray of another object. like here the main object is HotelDC and it has an array "features" an array of FeactureDC objects.
Here is my code:
- (id)initWithCoder:(NSCoder *)decoder {
self = [[HotelDC alloc] init];
if (self != nil) {
self.hotel_id = [decoder decodeIntegerForKey:#"hotel_id"];
self.name = [decoder decodeObjectForKey:#"name"];
self.features = [decoder decodeObjectForKey:#"features"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeInt:hotel_id forKey:#"hotel_id"];
[encoder encodeObject:name forKey:#"name"];
[encoder encodeObject:features forKey:#"features"];//its a mutable array
}
how should I save it, and retrieve?? I am getting error as
Attempt to insert non-property value '<HotelDC: 0xa600fe0>' of class 'HotelDC'.
Note that dictionaries and arrays in property lists must also contain only property values.
Solution :
//Setting
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:hotelObjSelected];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:myEncodedObject forKey:#"selectHotelObject"];
[[NSUserDefaults standardUserDefaults] synchronize];
// retrieving
NSData *data = [defaults objectForKey:#"selectHotelObject"];
hotelObjSelected = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSUserDefaults is backed by a property list. Alas, proprety lists cannot contain serialised objects. Quoting from the manual:
A default object must be a property list, that is, an instance of (or
for collections a combination of instances of): NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any
other type of object, you should typically archive it to create an
instance of NSData
You'll have to create your own serialised data file for saving the object directly, or serialise the objects as one of the allowed types. Annoyingly, NSUserDefaults doesn't call encodeWithCoder - it just screens the object type passed to setObject:forKey:. The best bet is to either serialise the fields of the HotelDC yourself, or archive the object to an NSData instance and store that.
I have did this by following way.check it. Below code is in for loop.
NSMutableArray *newEventArray = [[NSMutableArray alloc] init];
[newEventArray addObject:title];
[newEventArray addObject:alarmDate];
NSArray *iCalAlarmArray = [[NSUserDefaults standardUserDefaults] arrayForKey:#"alarmList"];
if(iCalAlarmArray == nil || [iCalAlarmArray count] <= 0)
{
iCalAlarmArray = [[NSMutableArray alloc] init];
}
iCalAlarmArray = [iCalAlarmArray arrayByAddingObject:newEventArray];
[[NSUserDefaults standardUserDefaults] setObject:iCalAlarmArray forKey:#"alarmList"];
[[NSUserDefaults standardUserDefaults] synchronize];
May this helps you.
you should write similiar encoding and decoding methods in FeatureDC and store it in an array and then encode here.
A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData

NSUserdefaults for multiuser

In my application, I am using a login form to enter into the application, also using NSUserDefaults to store user preferences, for example:
[storeData setObject:self.loginField.text forKey:#"USEREMAIL"];
[storeData setObject:self.PasswordField.text forKey:#"PASSWORD"];
Like I stored, if a new user logs in the NSUserDefaults stored value will be changed. But I want both preferences (ex:new userid and old userid as well as password). So please explain how to store multiple values for same key?
One way to solve this would be to store a NSDictionary with UserIDs as keys and the passwords as values.
Another option is to use Keychain as it specifically designed for this kind of thing and is also more secure.
Create a Global NSMutableArray and add above details in NSDictionary Objects and Store all Objects in array.This way you will have all user objects.You can get it whenever you want.
first of all create global array with AppDelegate class, for example..
userDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingtblArrayForSearch = [userDefaults objectForKey:#"arrScheduleDates"];
if (dataRepresentingtblArrayForSearch != nil) {
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingtblArrayForSearch];
if (oldSavedArray != nil)
arrScheduleDates = [[NSMutableArray alloc] initWithArray:oldSavedArray];
else
arrScheduleDates = [[NSMutableArray alloc] init];
} else {
arrScheduleDates = [[NSMutableArray alloc] init];
}
[arrScheduleDates retain];
after that when you want to store the new record then get all record from arrScheduleDates array and after that add the new record and after that store like whole array like above..
i hope you understand and its helpful for you...
:)
The absolute easiest way to meet your requirements is to use the user's email address (assuming they're all unique) as the storage key for your dictionary, and the password as the value.
If you need to store more than the password, then the value of the key would be another dictionary with keys and values for the user.
An example of the simple case would look similar to :
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSDictionary *storedUsers = [userDefaults objectForKey:#"userData"];
if (nil == storedUsers) storedUsers = [NSDictionary dictionary];
NSMutableDictionary *mutableStoredUsers = [NSMutableDictionary dictionaryWithDictionary:storedUsers];
NSString *userPassword = [self.PasswordField.text copy]; //add autorelease if you aren't using ARC
NSString *userEmail = [self.loginField.text copy]; //add autorelease if you aren't using ARC
if (nil != userEmail)
{
[mutableStoredUsers setObject:userPassword forKey:userEmail];
}
[userDefaults setObject:[NSDictionary dictionaryWithDictionary:mutableStoredUsers] forKey:#"userData"];
[userDefaults synchronize];
first download and import files of given link
https://github.com/ldandersen/scifihifi-iphone/tree/master/security
then where u want to store user preferences write this code
[SFHFKeychainUtils storeUsername:loginField.text andPassword:PasswordField.text forServiceName:#"dhaya" updateExisting:YES error:&error];
where u want password write this code
NSString *password = [SFHFKeychainUtils getPasswordForUsername:loginField.text andServiceName:#"dhaya" error:&error];
NSLog(#"passwordpassword %#",password);
this will working great....

Port array of strings to array of dictionaries each containing a string

I'm trying to port the strings in an array into an array of dictionaries where each dictionary contains one of the strings and a boolean. Hopefully this makes sense, but I'll try and make a diagram:
At the moment I have:
<array>
-<string1>
-<string2>
...
</array>
But I want:
<array>
-<dictionary1>
--<string1>
--<bool>
-</dictionary1>
-<dictionary2>
--<string2>
--<bool>
-</dictionary2>
...
</array>
I've tried creating a loop to cycle through the array of strings, but it doesn't seem to work.
The ultimate goal is to save this new array of dictionaries to the NSUserDefaults (which I'm also fairly unfamiliar with). This is what I have so far, and any help would be much appreciated!
// Get the current array from the user defaults.
NSArray *tempArray = [[NSUserDefaults standardUserDefaults] objectForKey:#"myArrayKey"];
self.myArray = [tempArray mutableCopy];
// Loop to cycle through the array of strings
for(int i = 0; i < [aList count]; i++)
{
// Boolean to go in the dictionary with the string.
checked = NO;
// Create a dictionary, and set it with two objects and two keys.
//First object is the string in the array we are cycling through, the second object is the boolean.
NSDictionary *tempDict = [[NSDictionary alloc] initWithObjectsAndKeys:[aList objectAtIndex:i], #"Title", checked, #"checked", nil];
// Add this dictionary to the new array of dictionaries.
[self.myArray addObject:tempDict];
// This NSLog gives me 0 - but gives me 0 seven times (the number of items in the array I am cycling through) so it is definitely cycling through the array.
NSLog(#"My Array Count: %i", [myArray count]);
// Release the tempDict.
[tempDict release];
}
// Write this new array of dictionaries back to NSUseDefaults.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:myArray forKey:#"myArrayKey"];
[defaults synchronize];
As Daniel pointed out (and I shortly realised through some tests), the fact that the array from NSUserDefaults was nil stopped it from working.
How about inserting a line
[tempDict retain];
next
[self.myArray addObject:tempDict];

Can't put a NSString into a NSMutableArray and store it in NSUserDefaults

I am receiving this error: '-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object'
In the ViewDidLoad I initialized my array:
id object = [[NSUserDefaults standardUserDefaults] objectForKey:#"array"];
if (object)
{
locationArray = object;
NSLog(#"retrieved", object);
}
else
{
locationArray = [[NSMutableArray alloc] init];
NSLog(#"init");
}
Then, I am trying to add the data to locationArray:
ABMultiValueRef multi = ABRecordCopyValue(person, property);
NSUserDefaults *locatie = [NSUserDefaults standardUserDefaults];
// Set up an NSArray and copy the values in.
NSArray *theArray = [(id)ABMultiValueCopyArrayOfAllValues(multi) autorelease];
//everything goes fine the first time, but the second time i receive an error after at this code:
[locationArray addObject:theArray];
[locatie setObject:locationArray forKey:#"array"];
Every first time I select an address everything is fine. But every second time I am receiving that error.
What did I do wrong?
NSString *fname = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSMutableString *lname = (NSMutableString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
What makes you think ABRecordCopyValue is going to return a mutable string here?
Just telling the compiler that it will return a mutable string (which is all “(NSMutableString *)” does) doesn't mean it will. If the Address Book documentation doesn't specify that this will return a mutable string, assume it won't and create a mutable string yourself.
Speaking of which…
NSMutableString *name = [[NSMutableString alloc] init];
Here's the string you should be appending to. You don't need lname to be mutable, because this string is mutable.
NSMutableString *space = #" ";
fname = [fname stringByAppendingFormat:space];
fname = [fname stringByAppendingFormat:lname];
By doing this, you waste the mutable string you created. You are creating two intermediate immutable strings here, not appending to the mutable string.
name = fname;
And here, you throw away the mutable string entirely (and thereby leak it since you never released it), replacing it with the immutable string you got from your series of stringByAppendingFormat: messages.
What you should do is send the name mutable string an appendFormat: message, passing both the first and last name strings. You don't need the space string; you can include that in the format string.
See also the NSMutableString docs.
[nameArray addObject:fname];
At no point prior to this statement have you created an NSMutableArray object and stored its pointer in the nameArray variable. Not in any code you've shown, anyway.
Until you do that, this variable holds nil, the pointer to no object. The addObject: message does nothing because that's what messages to nil do: Nothing. Logging the array you don't have produces “(null)” because that's the description of nil.
if (nameArray == nil) {
NSLog(#"NO DATA TO RETRIEVE FROM USERDEFAULTS");
You aren't showing any code that retrieves from user defaults. Even if you did, it would return an immutable array as Sven said; you would have to make a mutable copy.
This is the best I can do without a description of the problem. We may be able to provide more and better suggestions if you edit your question to tell us what happens when you run the above code, and not just what doesn't happen.
The problem occures when you get the locationArray from the NSUserDefaults and try to insert some object ([locationArray addObject:theArray];). The object you get from the NSUserDefaults is not mutable, so you have to create a mutable copy:
NSMutableArray* locationArray = [[NSUserDefaults standardUserDefaults] objectForKey:#"array"];
if (locationArray != nil)
locationArray = [[locationArray mutableCopy] autorelease];
else
locationArray = [NSMutableArray array];
And don't forget to check if fname is not nil before adding it to array