iOS encode/decode views and images to file in Documents directory - iphone

I have an object that has an NSMutableArray of custom UIViews, a UIImage, and a couple of strings.
I take that object and shove it into a dictionary. The dictionary gets shoved into an NSData then I do this:
NSString *path;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
path = [paths objectAtIndex:0];
NSString *fileName = DATA;
path = [path stringByAppendingPathComponent:fileName];
NSMutableData *data = [[NSMutableData alloc]init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
[archiver encodeObject:dict forKey: DATA];
[archiver finishEncoding];
[[NSFileManager defaultManager] createFileAtPath:path
contents:data
attributes:nil];
It saves successfully. When I try and load the data, the file system shows that my object was saved, but when I try and decode, it says my data is NULL.
Here is my decode code:
-(NSDictionary*)loadData
{
NSString *path;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
path = [paths objectAtIndex:0];
NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
if (directoryContent != nil && [directoryContent count] > 0)
{
NSLog(#"CONTENT COUNT: %d",[directoryContent count]);
NSString *fileName = DATA;
path = [path stringByAppendingPathComponent:fileName];
NSData *data = [[NSMutableData alloc]initWithContentsOfFile:[directoryContent objectAtIndex:0]];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSDictionary *dict = [unarchiver decodeObjectForKey: DATA];
[unarchiver finishDecoding];
return dict;
}
else
return nil;
}
My app allows the user to move a bunch of views around and I am just trying to find a simple way to store the Views frames and transforms without having to extract every int/string value. I also was hoping to not have to break down my UIImage into a byte array and then reload it back into an UIImage.
Am I doing crazy stuff here, hoping for functionality that isn't possible?
Is my only option to break down each UIView in the array into separate values for transforms, frames, etc?
Thoughts?

Does the document directory contain anything besides your data file? (I believe that it always contains at least a subdirectory named "Inbox".) I noticed these lines in your loadData method:
path = [path stringByAppendingPathComponent:fileName];
NSData *data = [[NSMutableData alloc]initWithContentsOfFile:[directoryContent objectAtIndex:0]];
You append fileName to path, but then you don't use it. You just read from the first file mentioned in directoryContent.

Related

loading Hierarchy in NSMutableData

Is there Hierarchy system in NSMutableData? If so, how do i load information in a hierarchy manner?
Example
//Create path to saving location
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSMutableData *gameData;
NSKeyedUnarchiver *decoder;
NSString *documentPath = [documentsDirectory stringByAppendingPathComponent:#"tierSave.dat"];
gameData = [NSData dataWithContentsOfFile:documentPath];
//end
//start loading
for(NSArray* firstData in gameData){
decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:gameData];
int level1 = [decoder decodeIntegerForKey:level1];
}
//end
However in for(NSArray* firstData in gameData), the compiler tells me that gameData will not respond to count because it is a NSMutableData instead of NSMutableArray etc, How do i exactly re-implement the code above?
Saving:
Create a NSArray with your multiple NSData
Use NSKeyedArchiver to make a NSData from your NSArray
Loading:
Load a NSData from file
Use NSKeyedUnarchiver to extract the NSArray
Loop your NSArray
In code:
NSData *data0 = [NSData data]; // Data 1
NSData *data1 = [NSData data]; // Data 2
/////////// SAVE
// now make a array
NSArray *array = [NSArray arrayWithObjects:data0, data1, nil];
// now archive the data
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:array];
// archivedData contains now a binary plist with your archived array including your NSData objects
[archivedData writeToFile:path atomically:YES];
/////// LOAD
NSData *archivedDataFromFile = [NSData dataWithContentsOfFile:path];
NSArray *newArray = [NSKeyedUnarchiver unarchiveObjectWithData:archivedDataFromFile];
NSData *dataLoaded0 = [newArray objectAtIndex:0];

How to Save NSMutableArray into plist in iphone

I am new in iphone, i want to save NSMutableArray data into plist file my Code is:
NSArray *array = [[NSArray alloc] initWithArray:self.artistDetailsArray];
[self.artistDetailsArray writeToFile:self.path atomically:YES];
but it shows 0 element in my plist path. please any help me.
Thanks in advance:
following is my code to store the data into plist and NSUserDefault.
none of them is working for NSMutableArray/NSArray but working for NSString. IS there any max size limit to store in plist or UserDefault??
NSMutableArray contains only text/ set of NSDictionary.
please suggest me.
- (void)initialiseDataFromLocalStorage
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
self.path = [documentsDirectory stringByAppendingPathComponent:#"saveLogin.plist"];
if ([fileManager fileExistsAtPath:self.path] == NO) {
NSString *pathToDefaultPlist = [[NSBundle mainBundle] pathForResource:#"saveLogin" ofType:#"plist"];
if ([fileManager copyItemAtPath:pathToDefaultPlist toPath:self.path error:&error] == NO) {
NSAssert1(0,#"Failed with error '%#'.", [error localizedDescription]);
}
}
// To get stored value from .plist
NSDictionary *dict = [[NSDictionary alloc]initWithContentsOfFile:self.path];
self.ResumedataDictionary = [[NSMutableDictionary alloc]initWithDictionary:dict];
[dict release];
self.artistDetailsArray = [[NSMutableArray alloc] initWithArray:[self.ResumedataDictionary objectForKey:#"artistDetailsDict"]];
// To get stored value from NSUserDefault
NSUserDefaults *fetchData = [NSUserDefaults standardUserDefaults];
self.artistDetailsArray = [fetchData objectForKey:#"artistDetailsDict"];
}
-(void)saveDataToLocalStorage
{
// To store value into .plist
NSArray *array = [[NSArray alloc] initWithArray:self.artistDetailsArray];
[ResumedataDictionary setObject:array forKey:#"artistDetailsDict"];
[ResumedataDictionary writeToFile:self.path atomically:YES];
// To store value into NSUserDefault
NSUserDefaults *fetchData = [NSUserDefaults standardUserDefaults];
[fetchData setObject:self.artistDetailsArray forKey:#"artistDetailsDict"];
}
NSMutableArray *array=[[NSMutableArray alloc] init];
[array addObject:#"test"];
[array writeToFile:#"/Users/parag/test.plist" atomically:YES];
[array release];
or
NSMutableArray *array=[[NSMutableArray alloc] init];
[array addObject:#"test121"];
id plist = [NSPropertyListSerialization dataFromPropertyList:(id)array format:NSPropertyListXMLFormat_v1_0 errorDescription:#error];
[plist writeToFile:#"/Users/parag/test.plist" atomically:YES];
Take a look at Creating Property Lists Programmatically
look at this code which creates path to plist in documents directory:
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); //1
NSString *documentsDirectory = [paths objectAtIndex:0]; //2
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"data.plist"]; //3
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: path]) //4
{
NSString *bundle = [[NSBundle mainBundle] pathForResource:#”data” ofType:#”plist”]; //5
[fileManager copyItemAtPath:bundle toPath: path error:&error]; //6
}
1) Create a list of paths.
2) Get a path to your documents directory from the list.
3) Create a full file path.
4) Check if file exists.
5) Get a path to your plist created before in bundle directory (by Xcode).
6) Copy this plist to your documents directory.
next read data:
NSMutableDictionary *savedStock = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
//load from savedStock example int value
int value;
value = [[savedStock objectForKey:#"value"] intValue];
[savedStock release];
write data:
NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
//here add elements to data file and write data to file
int value = 5;
[data setObject:[NSNumber numberWithInt:value] forKey:#”value”];
[data writeToFile: path atomically:YES];
[data release]
You can use property lists (NSPropertyListSerialization or writeToFile: way).
But be sure your array contains valid property list objects only (NSString, NSNumber, NSData, NSArray, or NSDictionary objects) and NSDictionary has only NSString keys. Custom (complex) objects have to be represented as dictionaries.
Or you should use approach with archives http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Archiving/Archiving.html through NSCoding protocol.
Nice guide is here http://cocoadevcentral.com/articles/000084.php
NSData *serializedData;
NSString *error;
serializedData = [NSPropertyListSerialization dataFromPropertyList:YourArray(You can use dictionaries.strings..and others too)
format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
if (serializedData) {
// Serialization was successful, write the data to the file system // Get an array of paths.
NSArray *documentDirectoryPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [NSString stringWithFormat:#”%#/serialized.xml”,
[documentDirectoryPath objectAtIndex:0]];
[serializedData writeToFile:docDir atomically:YES];
}
else {
// An error has occurred, log it
NSLog(#”Error: %#”,error); }
}

iPhone read/write .plist file

I'm making a application where I need to store some information the user have provided. I try to use a .plist file to store the information, I found this:
NSString *filePath = #"/Users/Denis/Documents/Xcode/iPhone/MLBB/data.plist";
NSMutableDictionary* plistDict = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
[plistDict setValue:#"Man" forKey:#"Gender"];
[plistDict writeToFile:filePath atomically: YES];
The problem is that the application will only work as long as I'm testing it in the iPhone simulator. I've tried this Changing Data in a Plist but without luck. I have also read something about that I need to add it to my bundle, but how?
New code:
- (IBAction)segmentControlChanged{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *plistLocation = [documentsDirectory stringByAppendingPathComponent:#"data.plist"];
NSMutableDictionary* plistDict = [[NSMutableDictionary alloc] initWithContentsOfFile:plistLocation];
if (Gender.selectedSegmentIndex == 0) {
[plistDict setObject:#"Man" forKey:#"Gender"];
[plistDict writeToFile:plistLocation atomically: YES];
}
else
{
[plistDict setObject:#"Women" forKey:#"Gender"];
[plistDict writeToFile:plistLocation atomically: YES];
}
}
I guess you have added your plist file to your resources folder in Xcode (where we place image, if not then you need to place that first). Resources data goes to [NSBundle mainBundle] by default and iOS does not allow us to change data inside bundle. So first you need to copy that file to Documents Directory.
Here is the code for copying file from NSBundle to the Documents directory.
- (NSString *)copyFileToDocumentDirectory:(NSString *)fileName {
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *documentDirPath = [documentsDir
stringByAppendingPathComponent:fileName];
NSArray *file = [fileName componentsSeparatedByString:#"."];
NSString *filePath = [[NSBundle mainBundle]
pathForResource:[file objectAtIndex:0]
ofType:[file lastObject]];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL success = [fileManager fileExistsAtPath:documentDirPath];
if (!success) {
success = [fileManager copyItemAtPath:filePath
toPath:documentDirPath
error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable txt file file with message \
'%#'.", [error localizedDescription]);
}
}
return documentDirPath;
}
Now you can use the returned documentDirPath to access that file and manipulate (Read/Write) over that.
The plist structure is:
<array>
<dict>key-value data</dict>
<dict>key-value data</dict>
</array>
Here is code to write data into plist file:
/* Code to write into file */
- (void)addToMyPlist {
// set file manager object
NSFileManager *manager = [NSFileManager defaultManager];
// check if file exists
NSString *plistPath = [self copyFileToDocumentDirectory:
#"MyPlistFile.plist"];
BOOL isExist = [manager fileExistsAtPath:plistPath];
// BOOL done = NO;
if (!isExist) {
// NSLog(#"MyPlistFile.plist does not exist");
// done = [manager copyItemAtPath:file toPath:fileName error:&error];
}
// NSLog(#"done: %d",done);
// get data from plist file
NSMutableArray * plistArray = [[NSMutableArray alloc]
initWithContentsOfFile:plistPath];
// create dictionary using array data and bookmarkKeysArray keys
NSArray *keysArray = [[NSArray alloc] initWithObjects:#"StudentNo", nil];
NSArray *valuesArray = [[NSArray alloc] initWithObjects:
[NSString stringWithFormat:#"1234"], nil];
NSDictionary plistDict = [[NSDictionary alloc]
initWithObjects:valuesArray
forKeys:keysArray];
[plistArray insertObject:poDict atIndex:0];
// write data to plist file
//BOOL isWritten = [plistArray writeToFile:plistPath atomically:YES];
[plistArray writeToFile:plistPath atomically:YES];
plistArray = nil;
// check for status
// NSLog(#" \n written == %d",isWritten);
}
Are you using that same path on your device? Apps on a device are sandboxed and can only read/write files in their documents directory. Grab that file path like this and append your plist name. This approach will also work on the simulator.
Try this:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *plistLocation = [documentsDirectory stringByAppendingPathComponent:#"myplist.plist"];

What am I doing wrong? NSFileManager woes

I'm currently building quite a large iPhone application. Bigger than I expected anyway. But that is beside the point, the overall idea of the application is to grab JSON from a web service, sort it all into custom NSObject's that are linked together and then present.
This goes all well and good. But, because I want the user to be able to see this information on the device without an internet connection, I need to save the information that I am presenting into the Documents folder that each Application has.
I basically implemented the NSCoding protocol into all the custom NSObject's that would need it in order to save it into a subdirectory of the Documents directory.
This is all achieved through this function here.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:#"Data"];
NSString *dataFileString = [dataPath stringByAppendingPathComponent:#"Company.archive"];
if (![[NSFileManager defaultManager] fileExistsAtPath:dataFileString]) //Does directory already exist?
{
MACompany *company = [MACompany sharedMACompany];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:#"Data"];
NSError *error;
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath]) //Does directory already exist?
{
if (![[NSFileManager defaultManager] createDirectoryAtPath:dataPath
withIntermediateDirectories:NO
attributes:nil
error:&error])
{
NSLog(#"Create directory error: %#", error);
}
}
NSString *dataFileString = [dataPath stringByAppendingPathComponent:#"Company.archive"];
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:company forKey:#"MACompany"];
[archiver finishEncoding];
[[NSFileManager defaultManager] createFileAtPath:dataFileString
contents:data
attributes:nil];
[archiver release];
[data release];
} else {
NSLog(#"File already exists, no need to recreate, not yet anyway");
}
}
I do the following request when the user first loads the application (application didFinishLaunchingWithOptions:) and when the user opens the application after being in the background (applicationWillEnterForeground:).
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:#"Data"];
NSString *dataFileString = [dataPath stringByAppendingPathComponent:#"Company.archive"];
if (![[NSFileManager defaultManager] fileExistsAtPath:dataFileString]) //Does directory already exist?
{
NSLog(#"Create a new Company Request");
MAWebRequests *companyReq = [[MAWebRequests alloc] init];
[companyReq getCompanyDetails];
[companyReq release];
} else {
NSLog(#"Saved Company Needs to be Decoded applicationWillEnterForeground");
MACompany *company = [MACompany sharedMACompany];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:#"Data"];
NSString *dataFileString = [dataPath stringByAppendingPathComponent:#"Company.archive"];
NSData *data = [[NSData alloc] initWithContentsOfFile:dataFileString];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
[data release];
company = [unarchiver decodeObjectForKey:#"MACompany"];
[unarchiver finishDecoding];
[unarchiver release];
}
Now this works all well and good and I can pull from this file also. But, I can only grab the data stored in this file when I have Xcode's debugger attached to the application. As soon as is stopped, the data is corrupted and doesn't include the original data.
The data is still stored there, I can see the created file, but the actual data itself that is stored within the file is wrong...
Should I not be using the above logic to save the data to the file and then pull recreate the shared object?
Has anyone else tried to do such a thing and had success?
Is there any reason as to why I'm running into this weird issue?
Has anyone else had this issue?
Any help would be greatly appreciated, have been trying all sorts of different methods to get it to work and nothing has been able to get there. All I need to be able to do is be able to store the data there permanently until I need to update it...
I have resolved this issue by saving the MACompany object well before applicationDidEnterBackground:
Major thanks to #OleBegemann for his aid in finding where the issue nested.

How to write to plist successfully?

I am having trouble writing my file into the plist after going through many tutorials, other people's problems and attempting it for myself. I can read the plist with no problems but I cant update it. Below are my codes on how I am writing my data into the plist. Correct me if I made any mistake.
NSString *path = [[NSBundle mainBundle] pathForResource:#"EventAddress" ofType:#"plist"];
NSMutableDictionary *myDictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
NSArray* allmyData = [myDictionary allValues];
// creates and array to store only the event details
NSMutableArray *data = [[NSMutableArray alloc] initWithArray:allmyData];
[data addObject:[[NSMutableDictionary alloc] initWithObjectsAndKeys:tfAddress.text, #"Address", tvEvents.text, #"Events", nil]];
[myDictionary setValue:data forKey:#"Whampo"];
BOOL flag = [myDictionary writeToFile:path atomically:YES];
if (flag){
NSLog(#"write to plist success");
}
NSLog(#"%#", myDictionary);
[myDictionary release];
The path is correct, the file exists, my values in the textView and textField are in the array, but when it comes to the writeToFile, it does not reflect on the file located at the document directory.
EDIT 01:
I found this online, very similar to Nekto's suggestion. But I am thinking on how to implement my code with his. I think its pretty simple, but I cant seem to figure out how to.
NSArray *paths= NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths objectAtIndex:0];
NSString *plistDirectory = [NSString stringWithFormat:#"%#/Enterprise",documentDirectory];
NSString *mPath = [plistDirectory stringByAppendingPathComponent:#"Downloads.plist"];
[mDownloadsArray writeToFile:mPath atomically:YES];
iphonesdk.blogspot taken from that site.
EDIT 02:
I used Nekto's suggestion and it worked well. But I am curious why it is returning DocumentsEventAddress.plist rather than EventAddress.plist. My assumption is because of the
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *plistPath = [rootPath stringByAppendingString:#"EventAddress.plist"];
Where rootPath is returning Document is that right?
I'm writing to file in such way:
NSString *errorDesc = nil;
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *plistPath = [rootPath stringByAppendingString:TEMPLATES_PATH];
NSDictionary *dict = [NSDictionary dictionaryWithObject:templates forKey:#"templates"];
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:dict format:NSPropertyListXMLFormat_v1_0 errorDescription:&errorDesc];
if (plistData)
{
[plistData writeToFile:plistPath atomically:YES];
}else
{
NSLog(#"[Error] Application Did Enter Background {saving file error}: %#", errorDesc);
[errorDesc release];
}
Be sure to save file in app documents directory.
I don't believe you can write to a resource. Only files in the documents directory can be written to.
You can't write to files that are located in you Bundle.
The app bundle is read only.