NSCoding - saving array to file or nsdefaults - ios

Afternoon all,
Working on my first iphone app.
I am trying to save an array of an array either to file or nsuserdefaults.
Data is like this...
MainArray (contains 3 below arrays)
Array1 (contains 3 strings)
Array2 (contains 3 strings)
Array3 (contains 3 strings)
So far I've been reading about saving things to nsuserdefaults, and saving to file. Not sure which is the right way or benefits of either but I start trying saving to file.
below is my custom object to save information.
#implementation UserSettingsClass
+ (instancetype)sharedUserData{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self loadInstance];
//sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
-(void)encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject:self.arrayUserSettings forKey:#"someArray"];
[encoder encodeObject:self.userDescription forKey:#"testDesc"];
}
- (id)initWithCoder:(NSCoder *)decoder{
if ((self = [super init])){
self.userDescription = [decoder decodeObjectForKey:#"testDesc"];
self.arrayUserSettings = [decoder decodeObjectForKey:#"someArray"];
}
return self;
}
+(NSString *)filePath{
static NSString *filePath = nil;
if (!filePath){
filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:#"gamedata"];
}
return filePath;
}
+(instancetype)loadInstance{
NSData *decodedData = [NSData dataWithContentsOfFile:[UserSettingsClass filePath]];
if (decodedData){
UserSettingsClass *gameData = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData];
return gameData;
}
return [[UserSettingsClass alloc] init];
}
-(void)save{
NSData *encodedData = [NSKeyedArchiver archivedDataWithRootObject:self];
[encodedData writeToFile:[UserSettingsClass filePath] atomically:YES];
}
and here is the main class where I am trying to use it.
//initialize variables
//_userArray = [[NSMutableArray alloc] init];
//_userDescription = [[NSString stringWithFormat:#"testDescription"] init];
//_userLoginID = [[NSString stringWithFormat:#"testLogin"] init];
//_userPW = [[NSString stringWithFormat:#"testPassword"] init];
// [_userArray addObject:[UserSettingsClass sharedUserData].userDescription];
//[_userArray addObject:_userLoginID];
//[[UserSettingsClass sharedUserData].arrayUserSettings addObject: [UserSettingsClass sharedUserData].userDescription];
NSMutableArray *tempArray = [UserSettingsClass sharedUserData].arrayUserSettings;
//[_userArray addObject:_userPW];
//save data to shared singleton class
//[[UserSettingsClass sharedUserData].arrayUserSettings addObject:_userArray];
//NSMutableArray *tempArray = [[NSMutableArray alloc] init];
//tempArray = [UserSettingsClass sharedUserData].arrayUserSettings;
//[UserSettingsClass sharedUserData].highScore = 10;
//int i = [UserSettingsClass sharedUserData].highScore;
//[UserSettingsClass sharedUserData].userDescription = #"hello";
NSString *temp2 = [UserSettingsClass sharedUserData].userDescription;
I am able to save the single string, but I must be doing something wrong.
The single string I saved was just to see if I can get it working. My goal is to save the main array to file (or nsuserdefaults), which contain about 3 objects (array)... and each of those arrays contains 3 strings each.
any I doing something blatantly wrong?

You are trying to hard.
If what you want to save is just NSArrays and NSStrings to you do not need so add an NSCoding, these types already conform to NSCoding. Just Archive to a file or "shudder" save to NSUserDefaults "/shudder".
It is really better to create a Data Model class and use NSArchiver to save and restore from a file in the Documents directory.

Related

How to split NSString and Rejoin it into two NSStrings?

I have a NSString like this one:
NSString* allSeats = #"1_Male,2_Female,3_Female,4_Male";
I want to split the NSString based on the keywords _Male & _Female and then make two separate strings like these:
NSString* maleSeats = #"1,4";
NSString* femaleSeats = #"2,3";
based on the contents of allSeats variable declared above.
How it will be possible to split NSString and then make 2 seperate strings?
You have to do it yourself. There is no "all done" solution. There are a few ways to do it.
Note: I didn't try my code, I just wrote it, it may don't even compile. But the important thing is that you get the whole idea behind it.
One way could be this one:
NSString *maleSufffix = #"_Male";
NSString *femaleSufffix = #"_Female";
NSMutableArray *femaleSeatsArray = [[NSMutableArray alloc] init];
NSMutableArray *maleSeatsArray = [[NSMutableArray alloc] init];
NSArray *array = [allSeats componentsSeparatedByString:#","];
for (NSString *aSeat in array)
{
if ([aSeat hasSuffix:maleSuffix])
{
[maleSeatsArray addObject:[aSeat stringByReplacingOccurencesOfString:maleSuffix withString:#""]];
}
else if ([aSeat hasSuffix:femaleSuffix])
{
[femalSeatsArray addObject:[aSeat stringByReplacingOccurencesOfString:femaleSuffix withString:#""]];
}
else
{
NSLog(#"Unknown: %#", aSeat);
}
}
NSString *maleSeats = [maleSeatsArray componentsJoinedByString:#","];
NSString *femaleSeats = [femaleSeatsArray componentsJoinedByString:#","];
Of course, you could use different methods on array, enumerating it, use a NSMutableString instead of a NSMutableArray (for femaleSeatsArray or maleSeatsArray, and use adequate methods then in the for loop).
I derived an idea from Larme's Clue and it works as :
Make a method as and call it anywhere :
-(void)seperateSeat
{
maleSufffix = #"_Male";
femaleSufffix = #"_Female";
femaleSeatsArray = [[NSMutableArray alloc] init];
maleSeatsArray = [[NSMutableArray alloc] init];
array = [self.selectedPassengerSeat componentsSeparatedByString:#","];
for (aSeat in array)
{
if ([aSeat hasSuffix:maleSufffix])
{
aSeat = [aSeat substringToIndex:[aSeat length]-5];
NSLog(#"%# is value in final seats ::",aSeat );
[maleSeatsArray addObject:aSeat];
}
else if ([aSeat hasSuffix:femaleSufffix])
{
aSeat = [aSeat substringToIndex:[aSeat length]-7];
NSLog(#"%# is value in final seats ::",aSeat );
[femaleSeatsArray addObject:aSeat];
}
}
totalMales = [maleSeatsArray componentsJoinedByString:#","];
totalFemales = [femaleSeatsArray componentsJoinedByString:#","];
NSLog(#"maleSeatsAre::::%#",totalMales);
NSLog(#"maleSeatsAre::::%#",totalFemales);
}

JSON with Dictionary - nested objects to convert to strings and display

I came across few posts here related to what I am doing but I am working with some nested objects that I want to extract.
This is a sample of my returned data - https://gist.github.com/ryancoughlin/8043604
I have this in my header so far :
#import "TideModel.h"
#protocol TideModel
#end
#implementation TideModel
-(id)initWithDict:(NSDictionary *)json {
self = [super init];
if(self) {
self.maxheight = [dictionary valueForKeyPath:#"tide.tideSummaryStats.minheight"];
self.minheight = [dictionary valueForKeyPath:#"tide.tideSummaryStats.maxheight"];
self.tideSite = [dictionary valueForKeyPath:#"tide.tideInfo.tideSite"];
}
return self;
}
#end
I have declared a property for each string and i am accessing it accordingly.
But what I have above doesn't work, maybe because it wont know what to drill in to correct?... Or will it?
tide.tideSummaryStats returns an array.
tide.tideInfo returns an array.
So you can't do -valueForKeyPath: all the way.
Also, this is incorrect: [dictionary valueForKeyPath:...];
it should be : [json valueForKeyPath:...];
because json is the name of the NSDictionary variable passed (not dictionary)
Try this (not sure):
-(id)initWithDict:(NSDictionary *)json {
self = [super init];
if(self) {
NSArray *arrOfTideSummaryStats = [json valueForKeyPath:#"tide.tideSummaryStats"];
NSDictionary *dctOfTideSummaryStats = [arrOfTideSummaryStats objectAtIndex:0];
//since self.maxheight and self.minheight are NSString objects and
//the original keys "minheight" & "maxheight" return float values, do:
self.maxheight = [NSString stringWithFormat:#"%f", [dctOfTideSummaryStats valueForKey: #"maxheight"]];
self.minheight = [NSString stringWithFormat:#"%f", [dctOfTideSummaryStats valueForKey: #"minheight"]];
/*============================================================*/
NSArray *arrOfTideInfo = [json valueForKeyPath:#"tide.tideInfo"];
NSDictionary *dctOfTideInfo = [arrOfTideInfo objectAtIndex:0];
self.tideSite = [dctOfTideInfo valueForKey:#"tideSite"];
}
return self;
}
Similar Questions:
How to parsing JSON object in iPhone SDK (XCode) using JSON-Framework
Getting array elements with valueForKeyPath
Keypath for first element in embedded NSArray
Recently had to create a app that worked with a remote RESTful server that returned JSON data and was then deserialised into an object for graphing.
I used unirest for the requests and responses and then deserialised the returned JSON into an object. Below is an extract of the code where "hourlySalesFigures" within dictionary "jsonResponseAsDictionary" was a JSON collection of 24 figures which I put into an array. Please note the function is a lot larger but I removed anything which I thought was distracting.
- (PBSSales*) deserializeJsonPacket2:(NSDictionary*)jsonResponseAsDictionary withCalenderType:(NSString *)calendarViewType
{
PBSSales *pbsData = [[PBSSales alloc] init];
if(jsonResponseAsDictionary != nil)
{
// Process the hourly sales figures if the day request and returned is related to Daily figures
if([calendarViewType isEqualToString:#"Day"]){
NSArray *hourlyFiguresFromJson = [jsonResponseAsDictionary objectForKey:#"hourlySalesFigures"];
PBSDataDaySales *tmpDataDay = [[PBSDataDaySales alloc] init];
NSMutableArray *hSalesFigures = [tmpDataDay hourlySalesFigures];
for(NSInteger i = 0; i < [hourlyFiguresFromJson count]; i++){
hSalesFigures[i] = hourlyFiguresFromJson[i];
}
[[pbsData dataDay] setHourlySalesFigures:hSalesFigures];
[pbsData setCalViewType:#"Day"];
}
}
return pbsData;
}

Saving Array easy way

I have used NSuserDefaults and NSkeyedArchive before but i dont think it will work for my new project..
I get data back from JSON and store it in an array (name,age,country) (all NSString)
i want to make a save button in the detail view so that it saves that person's data.
And show the saved data in another tableview. (for loop on the array and get all objects back)
How should i handle this in a easy way.. i except max 40 stored names so its not so heavy..
So in short i want a function like you see in "home app's" where you can "favorite/store a house"
-- Update
viewDidLoad
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [docDir stringByAppendingPathComponent:#"Names.plist"];
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
arrayWithNames = [[NSMutableArray alloc]init];
[arrayWithNames addObjectsFromArray:array];
Savebutton
NSMutableArray *nameInfo = [[NSMutableArray alloc]initWithObjects:self.name,self.age,self.country, nil];
[arrayWithNames addObjectsFromArray:nameInfo];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"Names.plist"];
[arrayWithNames writeToFile:path atomically:YES];
This works but i get all data together instead of every array as an independent object
btw i made sure there cant be a NULL :)
As long as all of the data is NSString values, as you say, you can just use writeToFile:atomically: to save an array to a file. However, JSON sometimes contains nulls, which aren't compatible with that method. If you try to use that method when nulls are present, it will throw an exception. If there's any chance of nulls (and there almost always is a chance), you'll need to take some precautions. A couple of possibilities:
Make mutable copies of your data, run through it, and remove nulls or replace them with something else (like an empty string).
Convert the data back to JSON via [NSJSONSerialization dataWithJSONObject:options:error:] and then write the resulting NSData to a file.
I not quite understand your question.
But in your case what I did was create a Model with the structure of information I intended to store (in your case looked Person) and created an array in which i will add the objects Person
Could use several cases to save, but in my opinion, the simplest would be through the NSUserDefaults (the solution depends heavily on your database).
Soo, you will have the model Person
import <Foundation/Foundation.h>
#interface Person : NSObject
#property(nonatomic,strong) NSString *name;
#property(nonatomic,strong) NSString *country;
#property(nonatomic,strong) NSString *age;
...
With the methods for the encryption:
- (void)encodeWithCoder:(NSCoder *)encoder {
//Encode properties, other class variables, etc
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeObject:self.age forKey:#"age"];
[encoder encodeObject:self.country forKey:#"country"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
//decode properties, other class vars
self.name = [decoder decodeObjectForKey:#"name "];
self.age = [decoder decodeObjectForKey:#"age"];
self.country = [decoder decodeObjectForKey:#"country"];
}
return self;
}
Then you create a NSMutableArray where you add your objects.
[arrayPeople addObject:person];
When you decide to store in your application data you can do this:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObjectShopping = [NSKeyedArchiver archivedDataWithRootObject:arrayPeople];
[defaults setObject:myEncodedObjectShopping forKey:#"people"];
To retrive the data:
NSData *myDecodedObject = [defaults objectForKey:#"people"];
NSMutableArray *decodedArray =[NSKeyedUnarchiver unarchiveObjectWithData: myDecodedObject];

NSMutableArray only has copies of the last object

I am using NSXML to parse out an XML document and add the results to an array of objects. The array has the correct number of objects, but they are full of data from the last object.(i.e. the object at index 0 has the same data as at index 3). I am getting good data back from my server.
//set up my objects and arrays higher in my structure
SignatureResult *currentSignatureResult = [[SignatureResult alloc]init];
Document *currentDoc = [[Document alloc]init];
Role *currentRole = [[Role alloc]init];
NSMutableArray *roleArray = [[NSMutableArray alloc] init];
NSMutableArray *doclistArray2 = [[NSMutableArray alloc] init];
.....there is more parsing up here
//role is defined as an NSXML Element
for (role in [roleList childrenNamed:#"role"]){
NSString *firstName =[role valueWithPath:#"firstName"];
NSString *lastName = [role valueWithPath:#"lastName"];
currentRole.name = [NSString stringWithFormat:#"%# %#",firstName, lastName];
for (documentList2 in [role childrenNamed:#"documentList"])
{
SMXMLElement *document = [documentList2 childNamed:#"document"];
currentDoc.name = [document attributeNamed:#"name"];
[doclistArray2 addObject:currentDoc];
}
currentRole.documentList = doclistArray2;
[roleArray addObject:currentRole];
///I've logged currentRole.name here and it shows the right information
}//end of second for statemnt
currentSignatureResult.roleList = roleArray;
}
///when I log my array here, it has the correct number of objects, but each is full of
///data from the last object I parsed
The cause is that the addObjects: retains for your currentRole object and not creates a copy from that. You can create your new currentRole object inside of the for or you can create a copy from that and add it to the array.
I recommend the following:
for (role in [roleList childrenNamed:#"role"]){
Role *currentRole = [[Role alloc] init];
NSString *firstName =[role valueWithPath:#"firstName"];
NSString *lastName = [role valueWithPath:#"lastName"];
currentRole.name = [NSString stringWithFormat:#"%# %#",firstName, lastName];
for (documentList2 in [role childrenNamed:#"documentList"])
{
SMXMLElement *document = [documentList2 childNamed:#"document"];
currentDoc.name = [document attributeNamed:#"name"];
[doclistArray2 addObject:currentDoc];
}
currentRole.documentList = doclistArray2;
[roleArray addObject:currentRole];
///I've logged currentRole.name here and it shows the right information
[currentRole release];
}//end of second for statemnt

NSCoding NSKeyedUnarchiver unarchiveObjectWithFile: returning null

I am trying to save some values from my app using NSCoding. I'm able to save the value but not able to retrieve it.
Here's where I am declaring the protocol:
#interface AddReminderEventViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource, NSCoding>
Here's where I'm complying with the protocol:
-(void)encodeWithCoder:(NSCoder *)enCoder
{
[enCoder encodeObject:self.eventRepeatDurationDate forKey:kEventRepeatDuration];
[enCoder encodeObject:self.eventIDsMutableArray forKey:kEventIDsMutableArray];
[enCoder encodeObject:self.eventRepeatDurationString forKey:#"mytest"];}
and here:
-(id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]){
self.eventRepeatDurationDate = [[decoder decodeObjectForKey:kEventRepeatDuration] retain];
self.eventIDsMutableArray = [[decoder decodeObjectForKey:kEventIDsMutableArray] retain];
self.eventRepeatDurationString = [[decoder decodeObjectForKey:#"mytest"] retain];} return self; }
and here's where I call the methods to do the archiving and unarchiving:
[self saveDataToDisk];
[self loadDataFromDisk];
and here are the bodies of these methods and it's NSLog contents:
- (void)saveDataToDisk {
NSString *reminderEventIDsPathString = #"~/Library/Application Support/ReminderIDs.archive";
//reminderEventIDsPathString = #"~/Library/Application Support/ReminderIDs.archive";
reminderEventIDsPathString = [reminderEventIDsPathString stringByExpandingTildeInPath];
NSLog(#"WATCH1: reminderEventIDsPathString is %#", reminderEventIDsPathString);
NSMutableDictionary *rootObject;
rootObject = [NSMutableDictionary dictionary];
[rootObject setValue:eventRepeatDurationString forKey:#"mytest"];
NSLog(#"1rootObject IS %#", rootObject);
[NSKeyedArchiver archiveRootObject:rootObject toFile:reminderEventIDsPathString];}
reminderEventIDsPathString is /Users/tester/Library/Application Support/iPhone Simulator/5.0/Applications/E26D57DE-C4E1-4318-AEDD-7207F41010A9/Library/Application Support/ReminderIDs.archive
2012-01-16 15:47:48.578 [29658:15503] 1rootObject IS {mytest = 7;}
and here is the unarchiver code along with its NSLog contents:
- (void)loadDataFromDisk {
NSString *testValue = [[NSString alloc] init];
NSString *reminderEventIDsPathString = #"~/Library/Application Support/ReminderIDs.archive";
reminderEventIDsPathString = [reminderEventIDsPathString stringByExpandingTildeInPath];
NSLog(#"WATCH2: reminderEventIDsPathString is %#", reminderEventIDsPathString);
NSMutableDictionary *rootObject;
rootObject = [[NSKeyedUnarchiver unarchiveObjectWithFile:reminderEventIDsPathString] retain];
NSLog(#"2rootObject IS %#", rootObject);
NSLog(#"WATCH3 - %#", [rootObject objectForKey:#"mytest" ]);
if ([rootObject valueForKey:#"mytest"]) {
testValue = [rootObject valueForKey:#"mytest"];
NSLog(#"WATCH: testValue is %#", testValue); } }
2012-01-16 15:48:14.965 [29658:15503] WATCH2: reminderEventIDsPathString is /Users/tester/Library/Application Support/iPhone Simulator/5.0/Applications/E26D57DE-C4E1-4318-AEDD-7207F41010A9/Library/Application Support/ReminderIDs.archive
2012-01-16 15:48:17.879 [29658:15503] 2rootObject IS (null)
What am I missing that I'm not able to unarchive the contents? I'm just focusing on the easiest of the values in my encoder/decoder methods just to test it but I'm not even able to get the string value to work.
Thanks
The path where you save and load your reminder is wrong. Maybe replace to this
NSString *reminderEventIDsPathString = [[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingPathComponent:#"ReminderIDs.archive"];

Resources