Writing an array of NSData to file - iphone

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).

Related

Write JSON Response to .plist File

Frustration on the Top !!!
I am getting some JSON Response from the Service and I want to store it in the .plist file for Future Reference.
I am unable to save my JSON Response to .plist File. I think it's due to some null values into the Response.
Note : I confirmed that the Response is in JSON Format using jsonparser.
My Code :
NSError *error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSDictionary *dictResult = [(NSDictionary*)json objectForKey:#"myKey"];
NSLog(#"Result Dictionary :: %#",dictResult);
NSURL *cacheDir = [[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *path = [cacheDir URLByAppendingPathComponent:#"FinalResult.plist"];
NSLog(#"Path :: %#",path);
BOOL success = [dictResult writeToURL:path atomically:YES];
NSLog(#"success? %d", success);
Note : I got all the NSLog Values (means the Response Dictionary and File Path but 0 for success).
Problem : There are almost 70-80 key-value pairs in the Response and I don't want to remove/replace all the null values. Because What I want is ...
GET the Response From the Server.
Fill all the UITextFields with the Response.
POST the Same Response to the Server with some Edited Values from the UITextFields.
So, I just want to change the Edited UITextField values in the Object and let it POST to the Server.
What is the Optimum Way to Fix this ?
I bet that your JSON contains at least one null value.
When you have JSON that contains null and you convert it using NSJSONSerialization, the null is replaced by an instance of NSNull. If your dictionary contains NSNull, then writeToURL:atomically: will fail.
This is because the convenience methods for reading and writing dictionaries (and arrays) only work if the data in the collection is restricted to property list types. These are:
NSString
NSNumber
NSData
NSDate
NSArray
NSDictionary. And for dictionaries, the keys must be NSStrings.
You can also use mutable subclasses (like NSMutableString) when writing.
If you have anything not on that list, you can't use writeToURL:atomically or any of the similar convenience methods.
The problem is that some valid JSON can't be converted to property lists. Also, some valid property lists can't be converted to JSON (because NSDate won't automatically convert to valid JSON).
If it was me, I'd just write the data to a JSON file. Leave it in its original format. You can convert to/from JSON easily, so leave it that way.
If your dictionary contains NSNull, then writeToURL:atomically: will fail.
For dictionaries, the keys must be NSStrings.
The problem is that some valid JSON can't be converted to property lists. Also, some valid property lists can't be converted to JSON.
Don't Forget, If you must use a property list, you will need to scan the entire dictionary and convert the nulls into something that can be saved in a property list file.
Only Solution is that you have to check all the NULL Values and Replace it with #" ".
Happy Coding...
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryPath = [paths objectAtIndex:0];
NSString *filename = #"FinalResult.plist";
NSString *pathFilename = [libraryPath stringByAppendingPathComponent:filename];
NSDictionary *dictResult = [json objectForKey:#"myKey"];
[dictResult writeToFile:pathFilename atomically:YES];
I build my file urls this way:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
NSString *filename = #"FinalResult.plist";
NSString *pathFilename = [path stringByAppendingPathComponent:filename];
Then see if this writes:
BOOL success = [dictResult writeToFile:pathFilename atomically:YES];
NSLog(#"success? %d", success);
Edit - Funny, I just recently confronted this problem and then forgot all about it. Some JSON parsers will use [NSNull null] as placeholders for nil values. I wrote this (seriously, just about two weeks ago and then spaced on it) to clean up the parse result...
- (NSDictionary *)compact:(NSDictionary *)aDictionary {
NSDictionary *answer = [NSMutableDictionary dictionary];
for (NSString *key in [aDictionary allKeys]) {
id value = [self.dictionary valueForKey:key];
if (value && value != [NSNull null]) {
[answer setValue:value forKey:key];
}
}
return [NSDictionary dictionaryWithDictionary:answer];
}
This could be made into a NSDictionary category addition if you wanted.
Check if the return value of the writeToURL:atomically: method returns YES.
If it doesn't, check if you have write permissions to that URL.
Try it with another URL to find out if its the path or the contents of the dictionary that's causing this error.
Try this, it will work :
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"country.plist"];
[jsonData writeToFile:path atomically:YES];

Objective-C - how to save and load an array containing images

Good evening everyone, I was hoping you could help with an Objective-C question I have.
In my app, I have a mutable array that contains 16 objects; the objects being images.
I would like to save and load the array so that the images are retained when the user quits the program.
I am new to data persistence and I can see there are several ways of saving and loading data and I am familiar with the NSUserDefaults method of saving and loading data. I am aware though that you cannot save arrays with images in this way.
Could someone please explain, perhaps with an example of the best and simplest way of saving and loading an array with images? Any help would be great as I'm unsure the best way to go with this.
Thanks everyone in advance!
Consider using NSKeyedArchiver.
// Archive
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:theArray];
NSString *path = #"/Users/Anne/Desktop/archive.dat";
[data writeToFile:path options:NSDataWritingAtomic error:nil];
// Unarchive
NSString *path = #"/Users/Anne/Desktop/archive.dat";
NSMutableArray *theArray = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
This way you can be sure the unarchived array is identical to the original.
All classes conforming to the NSCoding protocol can be used by NSKeyedArchiver.
Note: You can use any extension.
Response to comment:
The following should work on iOS:
// The Array
NSMutableArray * array = [[NSMutableArray alloc] init];
// Determine Path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [ [paths objectAtIndex:0] stringByAppendingPathComponent:#"archive.dat"];
// Archive Array
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
[data writeToFile:path options:NSDataWritingAtomic error:nil];
// Unarchive Array
NSMutableArray *theArray = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

Can I parse a XML file that is on a web server without downloading the file?

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.

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.)

Plist not saving from dictionary (to Documents)

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.