If I have a custom class Person which has three variables (which are propertized and synthesized):
NSString* theName;
float* theHeight;
int theAge;
Person instances are stored in an NSArray 'Group'. There is only one Group. What is the best way of storing and loading the Group in NSUserDefaults? (bearing in mind that float and int are not legal for NSUserDefaults)
#Brad Smith's answer is not complete, and even is incorrect in some sense. We have to use NSKeyedArchiver and NSKeyedUnarchiver as follows:
Make your class (for example in this case Person) to conform to protocol NSCoding and implement both the methods as:
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if(self) {
self.name = [decoder decodeObjectForKey:<key_for_property_name>];
// as height is a pointer
*self.height = [decoder decodeFloatForKey:<key_for_property_height>];
self.age = [decoder decodeIntForKey:<key_for_property_age>];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.name forKey:<key_for_property_name>];
//as height is a pointer
[encoder encodeFloat:*self.height forKey:<key_for_property_height>]
[encoder encodeInt:self.age forKey:<key_for_property_age>];
}
Using NSUserDefaults
// Write to NSUserDefaults
NSData *archivedObject = [NSKeyedArchiver archivedDataWithRootObject:<your_class_object>];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:archivedObject forKey:<key_for_archived_object>];
[defaults synchronize];
// Read from NSUserDefaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *archivedObject = [defaults objectForKey:<key_for_archived_object>];
<your_class> *obj = (<your_class> *)[NSKeyedUnarchiver unarchiveObjectWithData:archivedObject];
I find implementing encodeWithCoder and initWithCoder quite boring, especially if you have many attributes in your class, and many classes to save to NSUserDefaults.
I create a library RMMapper (https://github.com/roomorama/RMMapper) to help save custom object into NSUserDefaults easier and more convenient.
To mark a class as archivable, just use: #import "NSObject+RMArchivable.h"
To save a custom object into NSUserDefaults:
#import "NSUserDefaults+RMSaveCustomObject.h"
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults rm_setCustomObject:user forKey:#"SAVED_DATA"];
To get custom obj from NSUserDefaults:
user = [defaults rm_customObjectForKey:#"SAVED_DATA"];
Related
I need to save the OrderedDictionary to NSUserDefaults.
I read here and in many other posts how to do it:
-(id)initWithCoder:(NSCoder *)decoder{
if (self != nil){
dictionary = [decoder decodeObjectForKey:#"dictionary"];
array = [decoder decodeObjectForKey:#"array"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject: dictionary forKey:#"dictionary"];
[encoder encodeObject: array forKey:#"array"];
}
I then call it like this:
OrderedDictionary *od = [OrderedDictionary dictionaryWithDictionary:[businessInfo objectForKey:#"allowedStates"]];
NSData *archivedObject = [NSKeyedArchiver archivedDataWithRootObject:od];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:archivedObject forKey:#"allowedStates"];
[defaults synchronize];
and unarchive it like this:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *archivedObject = [defaults objectForKey:#"allowedStates"];
OrderedDictionary *countryStates = (OrderedDictionary *)[NSKeyedUnarchiver unarchiveObjectWithData:archivedObject];
I see that archivedObject is not empty, however countryStates is empty, although when I save it it's not empty.
How is it?
How can I archive successfully the OrderedDictionary?
EDITED
The method initWithCoder is being called, but not encodeWithCoder
Please refer below link, may be will you get proper solution.
encodeWithCoder is not called in derived class of the NSMutableDictionary
When does encodeWithCoder get called?
Good Luck !!!!
Make sure your class conforms to the <NSCoding> protocoll (so your class definition reads #interface OrderedDictionary <NSCoding>)
Else, the framework doesn't know it should call encodeWithCoder:
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
Hi I have my ALAsset URL save in NSMutableArray,
"ALAsset - Type:Photo, URLs:assets-library://asset/asset.JPG?id=119A0D2D-C267-4B69-A200-59890B2B0FE5&ext=JPG",
"ALAsset - Type:Photo, URLs:assets-library://asset/asset.JPG?id=92A7A24F-D54B-496E-B250-542BBE37BE8C&ext=JPG",
"ALAsset - Type:Photo, URLs:assets-library://asset/asset.JPG?id=77AC7205-68E6-4062-B80C-FC288DF96F24&ext=JPG
I wasnt able to save NSMutableArray in NSUserDefaults due to it having an error Note that dictionaries and arrays in property lists must also contain only property values.
Im thinking of using this :
- (void)encodeWithCoder:(NSCoder *)encoder {
//Encode properties, other class variables, etc
[encoder encodeObject:self.selectedPhotos forKey:#"selectedPhotos"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
//decode properties, other class vars
self.selectedPhotos = [decoder decodeObjectForKey:#"selectedPhotos"];
}
return self;
}
then save and retrieve it with this code:
- (void)saveCustomObject:(MyCustomObject *)obj {
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:obj];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:myEncodedObject forKey:#"myEncodedObjectKey"];
}
- (MyCustomObject *)loadCustomObjectWithKey:(NSString *)key {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [defaults objectForKey:key];
MyCustomObject *obj = (MyCustomObject *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
return obj;
}
But I somehow dont quite get it, still crashes in my code. Dont know how. And I wasnt able to save it in NSUserDefaults. Hope someone help. Really been having problem with this a while. Hope someone guide me on the right path of saving and retrieving it the right way from NSUserDefaults. Then back to a NSMutableArray.
The NSUserDefaults only takes a restricted set of classes as objects. See the documentation. You must take care only to store values of these types (NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary, and of course it applies recursively) in the dictionary.
To store the URLs in the NSUserDefaults, store them as strings, then read them back as URLs. If you need to have the dictionary in the current format, you may have to transform it before saving it.
- (void) saveMyUrls
{
NSMutableArray* urls = [NSMutableArray arrayWithCapacity:self.myUrls.count];
for(NSURL* url in self.myUrls) {
[urls addObject:[url absoluteString]];
}
[[NSUserDefaults standardUserDefaults] setObject:urls forKey:#"myUrls"];
}
- (void) loadUrls
{
NSArray* urls = [[NSUserDefaults standardUserDefaults] objectForKey:#"myUrls"];
self.myUrls = [NSMutableArray arrayWithCapacity:urls.count];
for(NSString* urlString in urls) {
[self.myUrls addObject:[NSURL URLWithString:urlString]];
}
[[NSUserDefaults standardUserDefaults] setObject:urls forKey:#"myUrls"];
}
If you need to save more information than just the URL, let's say a user-specified label, you could save the object as a NSDictionary instead, e.g.
- (void) saveMyUrlsWithLabels
{
NSMutableArray* objs = [NSMutableArray arrayWithCapacity:self.myObjects.count];
for(MyObject* obj in self.myObjects) {
[objs addObject:[NSDictionary dictionaryWithKeys:#"url", #"label"
forObjects:obj.url.absoluteString, obj.userSpecifiedLabel];
}
[[NSUserDefaults standardUserDefaults] setObject:objs forKey:#"myObjects"];
}
Maybe you should do it like this:
- (MyCustomObject *)loadCustomObjectWithKey:(NSString *)key {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults synchronize]; // note this
NSData *myEncodedObject = [defaults objectForKey:key];
MyCustomObject *obj = nil;
// it would be even better
// to wrap this into #try-#catch block
if(myEncodedObject)
{
obj = (MyCustomObject *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
}
return obj;
}
Also note that if you want to use NSKeyedArchiver and NSKeyedUNarchiver your MyCustomObject class has to conform to NSCoding protocol. Check NSCoding protocol reference and Archives and Serializations Programming Guide.
This is another way to do it and yes you can use NSUserDefaults. Basically you get asset URL, save it and then convert it back to an asset / image
//SET IT
ALAsset *asset3 = [self.assets objectAtIndex:[indexPath row]];
NSMutableString *testStr = [NSMutableString stringWithFormat:#"%#", asset3.defaultRepresentation.url];
//NSLog(#"testStr: %# ...", testStr);
[[NSUserDefaults standardUserDefaults] setObject:testStr forKey:#"userPhotoAsset"];
[[NSUserDefaults standardUserDefaults] synchronize];
//GET IT
NSString *assetUrlStr = [[NSUserDefaults standardUserDefaults] objectForKey:#"userPhotoAsset"];
NSURL* aURL = [NSURL URLWithString:assetUrlStr];
NSLog(#"aURL: %# ...", aURL);
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:aURL resultBlock:^(ALAsset *asset)
{
UIImage *copyOfOriginalImage = [UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage] scale:1.0 orientation:UIImageOrientationUp];
imgVwPortrait.image = copyOfOriginalImage;
}
failureBlock:^(NSError *error)
{
// error handling
NSLog(#"failure-----");
}];
I am making an app in which the user can select a song in a settings tab and have this played in a different view on demand. I want it so that this item can be stored if the user is to shut the app and reopen it another time.
I have managed to allow the user to select and store a song in with:
-(IBAction)showMediaPicker:(id)sender{
MPMediaPickerController *mediaPicker = [[MPMediaPickerController alloc] initWithMediaTypes: MPMediaTypeAny];
mediaPicker.delegate = self;
mediaPicker.allowsPickingMultipleItems = NO;
mediaPicker.prompt = #"Select Alarm Sound";
[self presentModalViewController:mediaPicker animated:YES];
}
- (void) mediaPicker: (MPMediaPickerController *) mediaPicker didPickMediaItems: (MPMediaItemCollection *) mediaItemCollection {
[self dismissModalViewControllerAnimated: YES];
settingsData.selectedSong = mediaItemCollection;//Object of type MPMediaItemCollection
but I want the user to have to do this every time they use the app.
I have tried using NSUserDefaults:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:settingsData.selectedSong forKey:#"alarmSoundKey"];
[defaults synchronize];
but get the error:
* -[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '' of class 'MPMediaItemCollection'. Note that dictionaries and arrays in property lists must also contain only property values.
What are my options please? Not really sure how to tackle this one...
SOLUTION -
I can't answer my own questions yet so I'll put it up here:
I HAVE FOUND MY OWN SOLUTION TO THIS:
First convert/encode the MPMediaItemCollection to an NSData Object and slam store it using NSUserDefaults using:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:mediaItemCollection];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:data forKey:#"someKey"];
[defaults synchronize];
From there, you can decode and use anywhere else in your app....
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:#"someKey"];
MPMediaItemCollection *mediaItemCollection = [NSKeyedUnarchiver unarchiveObjectWithData:data]
Hope that is some help to someone. Spread the word, this hasn't been covered enough. Have literally been working on this problem for about 4 hours...
You can only store property list values in NSUserDefaults. Since MPMediaItemCollection conforms to NSCoding you could use an NSKeyedArchiver to store it instead.
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSKeyedArchiver_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40003672
You then use NSKeyedUnarchiver to read it back out of the file later.
You can also use the MPMediaItemPropertyPersistentID property. You can form a query to retrieve the item from the iPod library when your application next launches, and gracefully handle things like when the user decides to remove the song from their library.
I have a class "MainClass". That class implements NSCoding protocol. In that class I have an array with objects of another class "Object", which also implements NSCoding protocol. When I archive an object of "MainClass" in NSUserDefaults and then unarchive it and try to get the object of class "Object" from array and use it properties then I get message:
[NSConcreteMutableData property]: unrecognized selector sent to instance.
What do I do wrong? How to fix it?
Edit:
This is code I use with NSUserDefaults:
- (Settings *) readData
{
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSettings = [currentDefaults objectForKey:#"data"];
if (dataRepresentingSettings != nil) {
return (MainClass *)[NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSettings];
}
return nil;
}
- (void) saveData
{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:self] forKey:#"data"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
There's two options :
(1) You're not retaining the Object that you got from the user defualts. When you get an object from NSUserDefaults it's autoreleased - if you want to keep it, you have to retain it.
(2) You've forgotten to unarchive it. You used NSKeyedArchiver to archive your Object but didn't unarchive it :)
you might have done something like
Object *object = [defaults objectForKey:#"object"];
instead of
NSData *data = [defaults objectForKey:#"object"];
Object *object = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NB Don't forget that object in my example is still autoreleased; if you want to keep it, you have to retain it ;)