I've been trying to save a plist of a NSDictionary to my app's Documents folder. I haven't tried this on the device yet but I'd like it to work on the simulator for testing purposes. The [self createDictionaryFromChoreList] method just creates a NSDictionary from some data in another class of mine. I've pretty much copied/pasted this code from the web documents and when I go to see if the file was saved or not, I find that it isn't. Here is the method block.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *plistName = [[NSString alloc] initWithFormat:#"%#chores.plist", self.firstName];
NSString *path = [documentsDirectory stringByAppendingPathComponent:plistName];
NSDictionary *choresDictionary = [[NSDictionary alloc] initWithDictionary:[self createDictionaryFromChoreList]];
[choresDictionary writeToFile:path atomically:YES];
Any help is greatly appreciated. Thanks in advance.
-S
You should also capture the BOOL returned by writeToFile:atomically:. That will tell you if the write succeeded or not.
Also, are you sure you are looking in the right documents folder? If you have more than one app in the simulator its easy to open the wrong app's documents folder in the Finder. I did that once and it cost me a couple of hours of frustration.
Edit01:
writeToFile:atomically: returning false explains why no file exist. The simplest explanation is that something in the dictionary is not a property list object.
From the NSDictionary docs:
This method recursively validates that
all the contained objects are property
list objects (instances of NSData,
NSDate, NSNumber, NSString, NSArray,
or NSDictionary) before writing out
the file, and returns NO if all the
objects are not property list objects,
since the resultant file would not be
a valid property list.
It just takes one non-plist object buried deep in a dictionary to prevent it from being converted to a plist.
Don't forget serialize the plist data:
Here is a snippet of code that I use for writing information to a plist
NSString *errorString;
NSData *data = [NSPropertyListSerialization dataFromPropertyList:plistDict
format:NSPropertyListXMLFormat_v1_0
errorDescription:&errorString];
[plistDict release];
if (!data) {
NSLog(#"error converting data: %#", errorString);
return NO;
}
if ([data writeToFile:[XEraseAppDelegate loadSessionPlist] atomically: YES]) {
return YES;
} else {
NSLog(#"couldn't write to new plist");
return NO;
}
This is something I whipped up really quickly and it correctly writes a plist directory of name and company to the documents directory. I have a feeling your dictionary creation method might have an issue. Try this out for yourself, then add your code and make sure it works.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *plistDirectory = [paths objectAtIndex:0];
NSString *plistPath = [plistDirectory stringByAppendingPathComponent:#"userCompany.plist"];
NSArray *userObjects = [[NSArray alloc] initWithObjects:#"Joe", #"Smith", #"Smith Co", nil];
NSArray *userKeys = [[NSArray alloc] initWithObjects:#"First Name", #"Last Name", #"Company", nil];
NSDictionary *userSettings = [[NSDictionary alloc] initWithObjects:userObjects forKeys:userKeys];
[userSettings writeToFile:plistPath atomically:YES];
Is it correct, that the name of file your writing to is:
SOEMTHINGchores.plist?
Created via:
NSString *plistName = [[NSString alloc] initWithFormat:#"%#chores.plist", self.firstName];
Also, what is the output of:
[choresDictionary print];
Some additional info would help to debug this.
Where exactly are you looking for the file?
I have the exact same code and it works fine for me.
Just that I have to dig deep to get the file. Something like:
/Users/myUserName/Library/Application Support/iPhone Simulator/User/Applications/0E62A607-8EEB-4970-B198-81CE4BDDB7AA/Documents/data.plist
And the HEX number in the path changes with every run. So I print the file path with every run.
Insert a break point at
NSDictionary *choresDictionary = [[NSDictionary alloc] initWithDictionary:[self createDictionaryFromChoreList]];
now when you step out drag your mouse over choresDictionary and check in the tooltip that its size is not 0x0 or you can simply do an NSLog of the choresDictionary
like NSLog(#"%#",choresDictionary); I think your dictionary has 0 key key value pairs thats why you are getting null into your documents folder.
Thanks,
Madhup
I was running into this issue as well. In my case it turned out that I was using NSNumbers for keys - which is not valid.
Related
I need to parse a XML file which is located on my server and return the node values without ever downloading the file to the device. Right now, the file is downloading to the device for testing purposes. Here is my current code:
+ (NSString *)dataFilePath:(BOOL)forSave {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *documentsPath = [documentsDirectory stringByAppendingPathComponent:#"file.xml"];
return documentsPath;
}
+ (NSString *)parse:(NSString *)nodesForPath:(NSString *)elementsForName {
NSString *filePath = [self dataFilePath:FALSE];
NSData *xmlData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
NSError *error;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
if (doc == nil) { return nil; }
ViewController *view = [[[ViewController alloc] init] autorelease];
NSArray *getVersionInfo = [doc nodesForXPath:nodesForPath error:nil];
for (GDataXMLElement *versionInfo in getVersionInfo) {
NSArray *elm1 = [versionInfo elementsForName:elementsForName];
GDataXMLElement *elm2 = (GDataXMLElement *) [elm1 objectAtIndex:0];
return elm2.stringValue;
}
[doc release];
[xmlData release];
}
It works fine but it parses the file in the document's directory. How would I set it up to parse directly from the web server? Thanks
The only way I can see to do this would be to run a program on the server to parse the file and return the result to the phone.
For the phone to run the code that parses the file it has to read the file which, by definition, will download it.
You could try using a pull parser for the XML and just work with the data received from the network as a stream, discarding unneeded contents.
You read a few bytes from the response, parse them, process, discard, read some more. You might have to make your own implementation of a pull parser because your dealing with incomplete, hence invalid, XML or to look up a parser that's tolerant to data ending (definitely not a validating one).
It might be worth-while to make your own implementation for the parser if you are dealing with really huge XML files. This way you can only store pieces of the XML that you need (you might need part of the XML 'tree' or none at all or you might need to retain other nodes referenced by another node in your XML).
I'm sorry for the generic answer, but I haven't used XML parsers on iOS yet and I'm not anywhere near a Mac atm to be able to test if NSXMLParser works for you.
Edit: Related post here.
Even if you don't want to save the file to your hard disk, you must have the contents of that file loaded on your RAM.
Convert NSData* to NSString, and then NSString to char* and give that to RapidXML.
I am trying to save an array of images to the documents folder. I managed to save an image as NSData and retrieve it using the method below, but saving an array seems to be beyond me. I've looked at several other questions that relate and it seems I'm doing everything right.
Adding the image as NSData and saving the image:
[imgsData addObject:UIImageJPEGRepresentation(img, 1.0)];
[imgsData writeToFile:dataFilePath atomically:YES];
Retrieving the data:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"imgs.dat"];
[self setDataFilePath:path];
NSFileManager *fileManager = [NSFileManager defaultManager];
if([fileManager fileExistsAtPath:dataFilePath])
imgsData = [[NSMutableArray alloc] initWithContentsOfFile:dataFilePath];
So, writing an image as NSData using the above works, but not an array of images as NSData. It inits the array, but it has 0 objects, which isn't correct, since the array I am saving has several. Does anyone have any ideas?
First of all, you should brush up Cocoa Memory Management, the first line of code is a little bit of a worry.
For data serialisation, you may like to have a go with NSPropertyListSerialization. This class serialises arrays, dictionaries, strings, dates, numbers and data objects. It has an error reporting system, unlike the initWithContentsOfFile: methods. The method names and arguments are a bit long to fit on one line, so sometimes you may see them written with Eastern Polish Christmas Tree notation. To save your imgsData object, you can use:
NSString *errString;
NSData *serialized =
[NSPropertyListSerialization dataFromPropertyList:imgsData
format:NSPropertyListBinaryFormat_v1_0
errorDescription:&errString];
[serialized writeToFile:dataFilePath atomically:YES];
if (errString)
{
NSLog(#"%#" errString);
[errString release]; // exception to the rules
}
To read it back in, use
NSString *errString;
NSData *serialized = [NSData dataWithContentsOfFile:dataFilePath];
// we provide NULL for format because we really don't care what format it is.
// or, if you do, provide the address of an NSPropertyListFormat type.
imgsData =
[NSPropertyListSerialization propertyListFromData:serialized
mutabilityOption:NSPropertyListMutableContainers
format:NULL
errorDescription:&errString];
if (errString)
{
NSLog(#"%#" errString);
[errString release]; // exception to the rules
}
Check the contents of errString to determine what went wrong. Keep in mind that these two methods are being deprecated in favour of the dataWithPropertyList:format:options:error: and propertyListWithData:options:format:error: methods, but these were added in Mac OS X 10.6 (I'm not sure if they're available on iOS).
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;
}
I've looked through the SDK documentation and through other questions that have been asked, and I am still a little confused on how exactly to do this. I had been previously been working with the following code, though it does not give the desired result of a .plist file. Besides mentioning the IBAction in the header files in this code in the .m file, is there anything else that needs to be added or anothe method I should be taking? Thanks!
My Code:
- (IBAction)fedDog {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"dogsFedDays.plist"];
NSMutableArray *dogsFedSave = [NSMutableArray arrayWithCapacity: 100];
for (int i = 0; i < 100; i++) {
NSDictionary *myDict = [[NSDictionary alloc] initWithObjectsAndKeys:
date[i], #"string",
fed[i], #"Yes",
nil];
[dogsFedSave addObject:myDict];
[myDict release];
}
if (![dogsFedSave writeToFile:path atomically:YES])
NSLog(#"not successful in completing this task");
}
I'm assuming that -writeToFile is returning NO so you're seeing your NSLog statement (correct me if I'm wrong). If that's the case, then the issue must be that some object in either your date array, or fed array is not any of the allowed object types for property lists which includes: NSString, NSData, NSArray, or NSDictionary. NSNulls are not allowed. From the docs for writeToFile:
This method recursively validates that
all the contained objects are property
list objects before writing out the
file, and returns NO if all the
objects are not property list objects,
since the resultant file would not be
a valid property list.
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.)