Strange bug with plist - iphone

I got a very strange bug when I'm trying to read my plist.
My plist looks like :
Root (Array)
Item 0 (Dictionary)
title (String)
I want to display title in the log, so I did the code bellow:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
NSString *path = [basePath stringByAppendingPathComponent:#"data.plist"];
NSMutableDictionary *dict = [[NSDictionary dictionaryWithContentsOfFile:path] mutableCopy];
NSLog(#"%#", [dict objectForKey:#"title"]);
NSLog(#"Path to plist: %#", path);
With this code, NSLog(#"%#", [dict objectForKey:#"title"]); is equal to "(null)"...
My plist is in my app's documents folder, and the log of path return the good path to my plist.
Help me please :)

The root of your plist is an array but you read it into a dictionary, thats won't work.
You should:
read the plist into an array
get item 0 (objectAtIndex:0) -> this is a dictionary
on this dictionary you can perform objectForKey ...

As #Tom said, (and as you said yourself in your first code block) the root of the plist is an array.
You can load an array very similarly using:
NSArray *array = [NSArray arrayWithContentsOfFile:path];
Then you can access the item at index 0 (the dictionary) like this:
NSDictionary *dict = [array objectAtIndex:0];
Or since Xcode 4.4 with thew new array literals:
NSDictionary *dict = array[0];
And then log the title as you already tried:
NSLog(#"%#", [dict objectForKey:#"title"]);
Or with the new syntax:
NSLog(#"%#", dict[#"title"]);

Related

How to save data on the next index of plist

im a newbie to iOS programming and getting my hands dirty on it. I searched on the net for some plist help and got something and understood it and used it but now im stuck at a point. I have searched alot for this problem. but im just unable to find the correct answer for me.
Problem:
My UI has just 2 text Fields and 1 Save Button.
1 textfield takes string while the other takes number (int) as input.
my plist has 1 dictionary item, that has 1 string item and 1 int item. thats it.
i take input in 2 UITextViews from user and save them into this plist via a save Button.
The problem is that whenever i enter new values and press the save button, it overwrites the old plist data.
I found out that i need to read the dictionary, append new values to it and then save it back in order to get my desired output. but im not able to grab this concept and put it into code. some code with explanation would really help.
my save button works like this:
-(IBAction)saveit
{
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"data.plist"];
// set the variables to the values in the UITextField text1 n text2 respectively
self.personName = text1.text;
num = (int)text2.text;
// create dictionary with values in UITextFields
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: personName, num, nil] forKeys:[NSArray arrayWithObjects: #"name", #"phone", nil]];
NSString *error = nil;
// create NSData from dictionary
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
// check is plistData exists
if(plistData)
{
// write plistData to our Data.plist file
[plistData writeToFile:plistPath atomically:YES];
}
else
{
NSLog(#"Error in saveData: %#", error);
// [error release];
}
}
Tthis code is working fine just that it is over writing new values. please help.
Your plist will return a dictionary.Retrieve that dictionary as NSMutableDictionary and add your new key pair value to that dictionary and then save it.
Save the data as an array of dictionaries like this
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"data.plist"];
//Load the original file
NSMutableArray *arr;
if([[NSFileManager defaultManager] fileExistsAtPath:plistPath])
//File exist load
arr = [[NSMutableArray alloc] initWithContentsOfFile:plistPath];
else
//File does not exist create
arr = [[NSMutableArray alloc] init];
//Create dictionary
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: personName, num, nil] forKeys:[NSArray arrayWithObjects: #"name", #"phone", nil]];
//Append to arr
[arr addObject:plistDict];
//Save to file
[arr writeToFile:plistPath atomically:YES];

Saving Array To File Objective C

I've been stuck on this for ever and I finally figured it out and now just out of the blue it stopped working again...
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/scoreCards.dgs",documentsDirectory];
NSMutableArray *savedArrayOfScorecards = [[NSMutableArray alloc]init];
savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
[savedArrayOfScorecards addObject:currentScoreCard];
[savedArrayOfScorecards writeToFile:filePath atomically:YES];
The file scoreCards.dgs is not even getting created...
What am I doing wrong?
There could be a couple things going wrong here.
1) The kind of data you're storing in the array might not be encodable or archive-able to a file. And the code snippet you included doesn't give a good hint as to what kind of data you're trying to save. If you have custom objects in your array (i.e. things that are not NSString, NSNumber, NSDate, etc.), then that's definitely the problem. There are plenty of questions here on StackOverflow that might help you solve this issue.
2) Your array's filepath could be bogus. For example, you're not checking to see if "documentsDirectory" is nil or valid or writeable.
3) Also possible, but not likely, "savedArrayOfScorecards" might be a nil array. You should do error checking to make sure "savedArrayOfScorecards" was instantiated and that there is more than one object in the array.
Your problem is, that although you create an array, before reading the file it is getting nil-ed on your call to:
savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
So, because this savedArrayOfScorecards is now nil, your call to write it to a file is not doing anything.
You should load the array to another variable, and check it being nil, and create the new array only if the one read from the file is nil. Something like this:
NSMutableArray *savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
if (!savedArrayOfScorecards) {
savedArrayOfScorecards = [[NSMutableArray alloc]init];
}
Are you sure the file exists when loading it?
savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
This line creates a new NSMutableArray from the file. If the file does not exist, it returns nil. writeToFile is then sent to nil and nothing would happen.
Add a check to see if it's nil and create a new array if it is:
NSMutableArray *savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
if(savedArrayOfScorecards == nil) savedArrayOfScorecards = [NSMutableArray array];
[savedArrayOfScorecards addObject:currentScoreCard];
[savedArrayOfScorecards writeToFile:filePath atomically:YES];
NSMutableArray is not a property-list-compliant format. You must use an NSArchiver to make it plist compliant.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/scoreCards.dgs",documentsDirectory];
NSMutableArray *savedArrayOfScorecards = [[NSMutableArray alloc]init];
savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
[savedArrayOfScorecards addObject:#"ALLLAALLAAALLA"];
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archive = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
[archive encodeObject:savedArrayOfScorecards forKey:#"Scorecards"];
[archive finishEncoding];
BOOL result = [data writeToFile:filePath atomically:YES];
NSLog(result ? #"YES" : #"NO");
The correct answers are already here, just adding a better solution:
NSFileManager* fileManager = [NSFileManager defaultManager];
NSMutableArray* array;
if ([fileManager fileExistsAtPath:filePath]) {
array = [NSMutableArray arrayWithContentsOfFile:filePath];
NSAssert(array != nil, #"Invalid data in file.");
}
else {
array = [[NSMutableArray] alloc] init];
}
[array addObject:currentScoreCard];
[array writeToFile:filePath atomically:YES];

Save NSMutable dictionary data in plist file _ integers as key

I'm currently trying to save a NSMutabledictionary keys and objects in a plist file. My keys are integers, so I'm using NSNumber to put them into the writeToFile function.
Even with that change, I cant find any of my saved data in the plist find. I suppose there is a problem with the NSNumber pointer because when I use a string it works.
do you have an idea of what is missing in my code?
NSMutableDictionary *dictionnaireNoms = [[NSMutableDictionary alloc] initWithCapacity:40];
NSNumber *nombre = [[NSNumber alloc] initWithInteger:dictionnaireNoms.count];
NSString *nomCommerce = text.text;
[dictionnaireNoms setObject:nomCommerce forKey:nombre];
//2. Sauvegarde du nom dans un fichier
[saveDicoCommerce enregisterNom:dictionnaireNoms];
- (void)enregisterNom:(NSMutableDictionary*)nom
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSLog(#"%#", documentsDirectory);
NSString *pathNom = [documentsDirectory stringByAppendingPathComponent:#"NomsDeCommerces.plist"];
if (!documentsDirectory) {
NSLog(#"Documents directory not found!");
return;
}
[nom writeToFile:pathNom atomically:YES];
if(![[NSFileManager defaultManager] fileExistsAtPath:pathNom])
{
NSLog(#"file not found");
return;
}
}
NSDictionary can only write itself directly if it only contains string keys. It confirms this you try to write using
[[NSPropertyListSerialization dataWithPropertyList:nom format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil] writeToFile:pathNom atomically:NO];`
The output is:
Property list invalid for format: 200 (property list dictionaries may only have keys which are CFStrings, not 'CFNumber')
However, you can store NSDictionary objects containing NSNumber keys if you serialize it using NSCoding. Replace this:
[nom writeToFile:pathNom atomically:YES];
with:
[NSKeyedArchiver archiveRootObject:nom toFile:pathNom];
To read the file created, use:
NSDictionary *nom2 = [NSKeyedUnarchiver unarchiveObjectWithFile:pathNom];
For more information about archives, see the Archives and Serializations Programming Guide.
Why do u want to allocate NSNumber? Try this [your_dictionary setValue:[NSNumber numberWithInt:your_int]];

Why doesen't it work to write this NSMutableArray to a plist?

edited.
Hey, I am trying to write an NSMutableArray to a plist.
The compiler does not show any errors, but it does not write to the plist anyway.
I have tried this on a real device too, not just the Simulator.
Basically, what this code does, is that when you click the accessoryView of a UITableViewCell, it gets the indexPath pressed, edits an NSMutableArray and tries to write that NSMutableArray to a plist. It then reloads the arrays mentioned (from multiple plists) and reloads the data in a UITableView from the arrays.
Code:
NSIndexPath *indexPath = [table indexPathForRowAtPoint:[[[event touchesForView:sender] anyObject] locationInView:table]];
[arrayFav removeObjectAtIndex:[arrayFav indexOfObject:[NSNumber numberWithInt:[[arraySub objectAtIndex:indexPath.row] intValue]]]];
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistPath = [rootPath stringByAppendingPathComponent:#"arrayFav.plist"];
NSLog(#"%# - %#", rootPath, plistPath);
[arrayFav writeToFile:plistPath atomically:YES];
// Reloads data into the arrays
[self loadDataFromPlists];
// Reloads data in tableView from arrays
[tableFarts reloadData];
CFShow() on the array after removing one of them shows this:
<CFArray 0x6262110 [0x2c810a0]>{type = mutable-small, count = 4, values = (
0 : <CFNumber 0x6502e10 [0x2c810a0]>{value = +3, type = kCFNumberSInt32Type}
1 : <CFNumber 0x6239de0 [0x2c810a0]>{value = +8, type = kCFNumberSInt32Type}
2 : <CFNumber 0x6239dc0 [0x2c810a0]>{value = +10, type = kCFNumberSInt32Type}
3 : <CFNumber 0x6261420 [0x2c810a0]>{value = +40, type = kCFNumberSInt64Type}
DEBUG-INFO: writeToPlist shows YES, I have tried to release all the arrays before filling them up again, setting them to nil, set atomically to NO.
As discussed in the comments below, the actual problem here is that the plist is being read from and written to two different locations. Somewhere in the app, there is code that reads the file into the array similar to this:
NSString *plistFavPath = [[NSBundle mainBundle] pathForResource:#"arrayFav"
ofType:#"plist"];
arrayFav = [[NSMutableArray alloc] initWithContentsOfFile:plistFavPath];
This logic reads the array from the application's bundle, which is a read-only location and part of the distributed app. Later when the edited array is persisted, code similar to this is used:
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES) objectAtIndex:0];
NSString *plistPath = [rootPath
stringByAppendingPathComponent:#"arrayFav.plist"];
NSLog(#"%# - %#", rootPath, plistPath);
[arrayFav writeToFile:plistPath atomically:YES];
The result here is that the updated file gets written to the app's documents directory, but it is never read from there, giving the appearance that the file is not being saved correctly. To correct this, you should change the code that reads the file to use the same path that you are writing to.
If you need to distribute a default version of the plist for use on the initial launch before the array has been edited, you could continue to include a version of the file in your bundle and then add code to your app delegate that check if the file exists in the documents directory and if it is not present, copies the bundle's default version of the file to the proper place.
[yourMutableArray writeToFile:fileName atomically:YES];
This should work. NSMutableArray inherits from NSArray which has a method to write to a plist.
writeToFile:atomically: won't work if your array contains custom objects.
If your array contains custom objects that are not Plist objects (NSArray, NSDictionary, NSString, NSNumber, etc), then you will not be able to use this method. This method only works on Plist objects.
Another option would be to use the NSCoding protocol, and write your objects to disk that way.
Yes
Look at the Property List Programming Guide.
phoneNumbers is a NSMutableArray
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
NSString *error;
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistPath = [rootPath stringByAppendingPathComponent:#"Data.plist"];
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects:
[NSArray arrayWithObjects: personName, phoneNumbers, nil]
forKeys:[NSArray arrayWithObjects: #"Name", #"Phones", nil]];
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict
format:NSPropertyListXMLFormat_v1_0
errorDescription:&error];
if(plistData) {
[plistData writeToFile:plistPath atomically:YES];
}
else {
NSLog(error);
[error release];
}
return NSTerminateNow;
}

IPHONE: Saving and Retrieving an Dictionary of Dictionaries from a plist

I have a main dictionary where each entry is a dictionary. I need to save this to a plist and then later retrieve its contents.
This is what I am doing to save the dictionary
// create a dictionary to store a fruit's characteristics
NSMutableDictionary *fruit = [[NSMutableDictionary alloc] init];
[fruit setObject:quantity forKey:#"quantity"];
[fruit setObject:productID forKey:#"productID"];
[fruit setObject:nameID forKey:#"nameID"];
// create a dictionary to store all fruits
NSMutableDictionary *stock = [[NSMutableDictionary alloc] init];
[stock setObject:fruit forKey:#"nameID"];
... after adding all fruits to the stock dictionary, write the stock to a plist
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"stock.plist"];
NSMutableDictionary *stock = [NSMutableDictionary dictionaryWithContentsOfFile:path];
[stock writeToFile:path atomically:YES];
... to restore the dictionary, I use
NSMutableDictionary *stock = [NSMutableDictionary dictionaryWithContentsOfFile:path];
... but this is not saving anything to the file... what am I missing?
thanks for any help.
You write:
... after adding all fruits to the
stock dictionary, write the stock to a
plist
but your code is reading from disk before you write the stock dictionary to disk. So with the assumption that stock.plist doesn't actually exist at that path, you've just set stock to nil, so after that you're sending the writeToFilePath message to nil.
try this:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"stock.plist"];
// write plist to disk
[stock writeToFile:path atomically:YES];
// read it back in with different dictionary variable
NSMutableDictionary *savedStock = [NSMutableDictionary dictionaryWithContentsOfFile:path];
if( savedStock==nil ){
NSLog(#"failed to retrieve dictionary from disk");
}
Finally, what data types are quantity and productID? you cannot serialize non-object data types, so if quantity is an integer, you would need to wrap it like so:
[fruit setObject:[NSNumber numberWithInt:quantity] forKey:#"quantity"];
Spend some time reading about property list serialization.
dictionaryWithContentsOfFile doesn't save, it reads a file. I don't see any code which writes to the file.
You are going to need something like this in your save code:
[stock writeToFile:path atomically:YES];
You're (re-)creating stock with the contents of the file immediately before writing it. Since the file doesn't exist, the dictionary is now nil. When you attempt to write that out, it doesn't produce anything. Instead, you should use the version of stock that you already populated.
(Assuming the saving bit is in the same scope, just delete the line starting NSMutableDictionary *stock above the call to writeToFile.)
(Although, come to think of it, it can't be in the same scope or the compiler would have complained in the first place.)