I have an array of custom objects which I want to store in array that updates the UITableView. The array is quite small ~10 objects at most. However, I discovered that I am unable to save an array of custom objects because:
Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')
2015-01-04 17:56:33.414 fanSyncDemo[13985:1264828] Attempt to set a non-property-list object (
It looks like I am going to have to turn the objects into NSData objects using NSKeyArchiver. However, I am having trouble understanding how this will look. Could someone help me with an example? My code for the saving looks like this:
- (IBAction)savePressed:(id)sender {
if (_NotificationsSegmentedControl.selectedSegmentIndex == 0){
_notificationBool = #"Yes";
}
else{
_notificationBool = #"No";
}
MyFavorite *favorite = [[MyFavorite alloc] initWithName:_nameFavoriteTextField.text withLocation:#"FANLOCATION" withIsOnNotificationsScreen:_notificationBool];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *favoritesList = [[NSMutableArray alloc] initWithArray: [defaults objectForKey:#"favoritesList"]];
[favoritesList addObject:favorite];
[defaults setObject:favoritesList forKey:#"favoritesList"];
[defaults synchronize];
}
This is not a duplicate of this question because that example doesn't explain how to retrieve the data, nor can I figure out how to apply it to my example which normally I don't have a problem with but I have just been struggling with this.
From what I understand your going to want to do something like this in your MyFavorite class
- (id)initWithCoder:(NSCoder *)aDecoder
{
_location = [aDecoder decodeObjectForKey:#"location"];
_isOnNotificationsScreen = [aDecoder decodeObjectForKey:#"notificationScreen"];
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.location forKey:#"location"];
[aCoder encodeObject:self.isOnNotificationsScreen forKey:#"notificationScreen"];
}
Related
This question already has answers here:
How to save custom objects in array and store it in NSUserDefaults - iPhone
(3 answers)
Closed 5 years ago.
I've seen a few posts about how you can't save an object based NSMutableArray in NSUserDefaults, but that there is a way of doing it using [NSKeyedArchiver] and encoding it with NSCoder although I'm struggling to understand how to do it and was hoping someone could help.
Student *student1 = [Student studentWithImage:[UIImage imageNamed:#"default"] withForename:#"John" withSurname:#"Smith" withAddress:#"3 Fake Road, Faketown, FA31 KEE" withDateOfBirth:[dateFormatter dateFromString:#"17-Jul-93"] withAge:24];
[studentArray addObject:student1];
I want this and a few other similar Students to be saved to my NSMutableArray studentArray. I want to then save this array to NSUserDefaults and load it back up again.
I have seen this post here which looks like is the correct answer to my question, but I need help implementing it to my code as I'm having difficulty understanding it!
Thanks
First add this in Student class
- (void)encodeWithCoder:(NSCoder *)coder;
{
[coder encodeObject:self.Forename forKey:#"keyForename"];
// do same for all property
}
- (id)initWithCoder:(NSCoder *)coder;
{
self = [[Student alloc] init];
if (self != nil)
{
self.Forename = [coder decodeObjectForKey:#"keyForename"];
// do same other property here
}
return self;
}
Store Array
[studentArray addObject:student1];
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:studentArray] forKey:#"mySavedArray"];
Retrieve Array
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *savedArray = [currentDefaults objectForKey:#"mySavedArray"];
if (savedArray != nil)
{
NSArray *oldArray = [NSKeyedUnarchiver unarchiveObjectWithData:savedArray];
if (oldArray != nil) {
customObjectArray = [[NSMutableArray alloc] initWithArray:oldArray];
} else {
customObjectArray = [[NSMutableArray alloc] init];
}
}
PFQuery *Location = [PFQuery queryWithClassName:#"Location"];
[Location findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
NSLog(#"%#", [objects objectAtIndex:0]);
}];
How can i store this object in NSUserDefaults?
For storing custom objects u need to add these two methods in your m file of custom object class
-(void)encodeWithCoder:(NSCoder *)encoder
{
//Encode the properties of the object
[encoder encodeObject:self.contact_fname forKey:#"contact_fname"];
[encoder encodeObject:self.contact_lname forKey:#"contact_lname"];
[encoder encodeObject:self.contact_image forKey:#"contact_image"];
[encoder encodeObject:self.contact_phone_number forKey:#"contact_phone_number"];
}
-(id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if ( self != nil )
{
//decode the properties
self.contact_fname = [decoder decodeObjectForKey:#"contact_fname"];
self.contact_lname = [decoder decodeObjectForKey:#"contact_lname"];
self.contact_image = [decoder decodeObjectForKey:#"contact_image"];
self.contact_phone_number = [decoder decodeObjectForKey:#"contact_phone_number"];
}
return self;
}
then
-(void)writeArrayWithCustomObjToUserDefaults:(NSString *)keyName withArray:(NSMutableArray *)myArray
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:myArray];
[defaults setObject:data forKey:keyName];
[defaults synchronize];
}
-(NSArray *)readArrayWithCustomObjFromUserDefaults:(NSString*)keyName
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:keyName];
NSArray *myArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
[defaults synchronize];
return myArray;
}
use these functions for storing and reading custom object arrays
or u could simply use this library https://github.com/roomorama/RMMapper
There is a constraint on what kind of objects can be stored in the user defaults, detailed on the Apple documentation page:
The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects. See What is a Property List? in Property List Programming Guide.
You will need to convert your array of objects to an array of dictionaries, one of the ways to achieve this is to implement a toDictionary method that will take all properties and put them into a dictionary.
You'll also need the reverse method, an initWithDictionary: if you'll also want to re-create the objects from the user defaults (you can use the converted dictionaries if you want, though).
Once you have the conversion methods, you can use the NSUserDefaults methods to store and retrieve the objects.
Let me preface that my code has been obscured a little on purpose. I know the name won't be good. I am trying to add a widget to my app with the new iOS 8 functionality. I am using this link as a tutorial
Now, so far I have this in my ViewController when my submit button is called in my app. By this time I have all my data in that array to be passed on.
//Passes the array of times to the shared group to be called by the notification center widget.
NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.myGroup.TodayExtensionSharingDefaults"];
NSData *encodedArray = [NSKeyedArchiver archivedDataWithRootObject:tableViewController.TimeArray];
[sharedDefaults setObject:encodedArray forKey:#"TimesArray"];
[sharedDefaults synchronize];
However, my array stores a custom data model I created, so in my data model I have:
- (void)encodeWithCoder:(NSCoder *)encoder {
//Encode properties, other class variables, etc
[encoder encodeInt:_stopNumber forKey:#"stopNumber"];
[encoder encodeObject:_route forKey:#"route"];
[encoder encodeObject:_number forKey:#"number"];
[encoder encodeInt:_time forKey:#"time"];
[encoder encodeObject:_minutesOrApproaching forKey:#"#minutesOrApproaching"];
[encoder encodeObject:_noPrediction forKey:#"noPrediction"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if(self) {
//decode properties, other class vars
_stopNumber = [decoder decodeIntForKey:#"stopNumber"];
_route = [decoder decodeObjectForKey:#"route"];
_number = [decoder decodeObjectForKey:#"number"];
_time = [decoder decodeIntForKey:#"time"];
_minutesOrApproaching = [decoder decodeObjectForKey:#"minutesOrApproaching"];
_noPrediction = [decoder decodeObjectForKey:#"noPrediction"];
}
return self;
}
And then in my Widget I have this code being called:
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.myGroup.TodayExtensionSharingDefaults"];
NSData *myDecodedObject = [defaults objectForKey: #"TimesArray"];
NSArray *decodedArray =[NSKeyedUnarchiver unarchiveObjectWithData: myDecodedObject];
int i = 0;
for (ETA *eta in decodedArray) {
if(i == 0){
_stopNumber.text = eta.number;
}
UILabel *label = [timeSlots objectAtIndex:i];
label.text = eta.minutesOrApproaching;
i++;
}
My issue is that I keep getting:
* Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*
-[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (ETA)'
enter code here
So I am a little off put because I don't know what I am doing wrong here. It would be really great if someone could help point me in the right direction. Thanks again!
So, turns out passing custom classes can be a pain. And although all the code seems to be right, I am still getting the error. So I stepped back and looked at my custom object and asked myself "What is it that I really need from this?" and using that process decided I only needed certain values, so with that I encrypted only those values and not the object. Since those values were ints and strings, they are built into iOS on how to encode and decode. From there, I was able to pass the values around thanks to #meda's answer and my idea.
I have an array that is broken down like this:
PersonArray
PersonObject
1. NSstring (personsName)
2. NSMutableArray (EventsObject)
I am able to save this array with the code below:
NSMutableArray *archiveArray = [NSMutableArray arrayWithCapacity:mutableDataArray.count];
for (BC_Person *personObject in mutableDataArray)
{
NSData *personEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:personObject];
[archiveArray addObject:personEncodedObject];
}
NSUserDefaults *userData = [NSUserDefaults standardUserDefaults];
[userData setObject:archiveArray forKey:#"personDataArray"];
[userData synchronize];
What I am stuck on is: If I add an object to the NSMutableArray inside of the Person object, do I also have to turn that array of object (EventsObject) into NSData?
(I would assume I do, but I can't seem to figure out how to target that array of objects (EventsObject) to convert inside the PersonArray.
I hope I am explaining this in a manner that is easy to understand.
NSArray and some other classes implement the NSCoding protocol. So you can simplify your code to:
NSData *personData = [NSKeyedArchiver archivedDataWithRootObject:mutableDataArray];
NSUserDefaults *userData = [NSUserDefaults standardUserDefaults];
[userData setObject:personData forKey:#"personDataArray"];
[userData synchronize];
And for any objects to be archived you implement the NSCoding protocol. So for BC_Person it could look like this (I do not know the class so I am making up some properties as an example):
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.firstName forKey:#"firstName"];
[encoder encodeObject:self.lastName forKey:#"lastName"];
[encoder encodeObject:self.eventObjects forKey:#"eventObjects"];
}
In this example self.eventObjects would be an array of objects of your EventsObject class. The array knows how to encode itself, and for EventsObject you implement another -encodeWithCoder: method.
And to unarchive you reverse the process.
I am new in programming and i have one problem. I create app, with two views. First is tableviewcontroller and second is view controller. from second view i sending data into my tableview trough perepareForSegue method. My data (Strings) are NSMutableArray and this MutableArray i want to save into UsedDefault after my app didEnterBackgoung in appDelegate.m
Then i want to load it back, but nothing happened. I put my code in appDidBecomeActive in appDelegate.m and i already try write it in my first view viewDidLoad, but no action. My MuttableArray is still empty, after new app start.
here is my code:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSUserDefaults*defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:self.flipsideView.zoznamFunkcii forKey:#"NSMutableArray"];
[defaults synchronize];
}
flipsideView is my first View and zoznamFunkcii is my NSMuttable array
and this is my load method:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSUserDefaults*defaults = [NSUserDefaults standardUserDefaults];
self.flipsideView.zoznamFunkcii = [NSMutableArray arrayWithArray:[defaults objectForKey:#"NSMutableArray"]];
}
Can anyone help me with that? I don't know why it not work.
You are saving an empty NSMutableArray to NSUserDefaults. So of course the loaded array will be empty as well.
Here is your saving code with comments:
// overwrite zoznamFunkcii with empty array
self.flipsideView.zoznamFunkcii = [[NSMutableArray alloc]init];
NSUserDefaults*defaults = [NSUserDefaults standardUserDefaults];
// save this empty array to nsuserdefaults
[defaults setObject:self.flipsideView.zoznamFunkcii forKey:#"NSMutableArray"];
your saving code should probably look like this:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSUserDefaults*defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:self.flipsideView.zoznamFunkcii forKey:#"NSMutableArray"];
}
and because NSUserDefault does not save mutable objects your restoration code should look like this:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSUserDefaults*defaults = [NSUserDefaults standardUserDefaults];
self.flipsideView.zoznamFunkcii = [NSMutableArray arrayWithArray:[defaults objectForKey:#"NSMutableArray"]];
}
and if the objects inside your array are not instances of NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary you have to encode your array first. See this question for details on how to do that.
Here you can Save Object With NSUserDefault..
[[NSUserDefaults standardUserDefaults]setObject:[NSKeyedArchiver archivedDataWithRootObject:self.flipsideView.zoznamFunkcii] forKey:#"NSMutableArray"];
[[NSUserDefaults standardUserDefaults]synchronize];
and you make coder and decoder method
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
self.toolResult = [decoder decodeObjectForKey:#"obj1"];
self.toolNotes = [decoder decodeObjectForKey:#"obj2"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.toolResult forKey:#"obj1"];
[encoder encodeObject:self.toolNotes forKey:#"obj2"];
}
your Data Save Into NsuserDefault When you Get Data From NsUserDefault then you Use like
[NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:#"NSMutableArray"]];