iOS - How to Save to and Read from file using Archiving? - objective-c

I'am new to Xcode and I really need help on this.
I'm trying to use Archiving to save a file with an array of items and trying to read and put them in a table view.
The problem is that when I close the app and then launch it again, when I save the data into the file it overwrites the existing file and all the stored data goes away.
I'm using this methods to store data in the file:
-(id)init
{
if(self = [super init])
{
_appSupportPath = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)lastObject];
if (!_appSupportPath)
return nil;
_repositoryPath = [_appSupportPath stringByAppendingPathComponent:FILE_NAME];
if (_data == nil)
_data = [[NSMutableDictionary alloc] init];
}
return self;
}
-(void)saveToFile
{
dispatch_queue_t writeQueue = dispatch_queue_create("MyApp:file:write", NULL);
NSMutableDictionary *localData = [NSMutableDictionary dictionaryWithDictionary:_data];
dispatch_async(writeQueue, ^{
NSFileManager *fileManager = [NSFileManager defaultManager];
if(![fileManager fileExistsAtPath:_appSupportPath])
[fileManager createDirectoryAtPath:_appSupportPath withIntermediateDirectories:YES attributes:nil error:NULL];
[NSKeyedArchiver archiveRootObject:localData toFile:_repositoryPath];
});
}
Any help?
Please let me know if there's a better/easier way of storing files that I'm not aware of.
Thanks in advance

You save it correctly.
Read it on launch in the init:
_data = [[NSKeyedUnarchiver unarchiveObjectWithFile:_repositoryPath] retain];
if (_data == nil)
_data = [[NSMutableDictionary alloc] init];

Related

Vfr-Reader crashes on assert(range.location != NSNotFound);

I am using thisopen source control called Vfr Reader to show pdf files in my app. All was working on iOS 7. After iOS update app crashes when I try on open a pdf file.
I am using this code to initialize pdf reader.
NSString *fileName = [[NSBundle mainBundle] pathForResource:#"PdfName" ofType:#"pdf"];
ReaderDocument *document = [ReaderDocument withDocumentFilePath:fileName password:nil];
if (document != nil)
{
ReaderViewController *readerViewController = [[ReaderViewController alloc] initWithReaderDocument:document];
readerViewController.delegate = self;
readerViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
readerViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:readerViewController animated:YES completion:Nil];
}
App crashes in this fuction of vfr
+ (NSString *)relativeFilePath:(NSString *)fullFilePath
{
assert(fullFilePath != nil); // Ensure that the full file path is not nil
NSString *applicationPath = [ReaderDocument applicationPath]; // Get the application path
NSRange range = [fullFilePath rangeOfString:applicationPath]; // Look for the application path
assert(range.location != NSNotFound); // **Crashes here**
return [fullFilePath stringByReplacingCharactersInRange:range withString:#""]; // Strip it out
}
On crash debugger shows these values.
fullFilePath = #"/Users/GuruTraxiOSDev01/Library/Developer/CoreSimulator/Devices/CC9412A6-9A95-4F46-89BA-8ECC13D0AF19/data/Containers/Bundle/Application/D2DC440B-F010-4575-93FD-3CB05BFF4F78/AppName.app/PdfName.pdf" 0x798c9b30
range = location=2147483647, length=0
applicationPath =#"/Users/GuruTraxiOSDev01/Library/Developer/CoreSimulator/Devices/CC9412A6-9A95-4F46-89BA-8ECC13D0AF19/data/Containers/Data/Application/32D612DE-FFD2-4C1E-B403-CDA177B460A6" 0x798b46b0
I already confirmed the file's existence.
Can anyone help please!
EDIT: This question solved crash on file load. But app still crashes on
CGContextDrawPDFPage(context, thePDFPageRef);
I was facing the same issue, so I made some changes to the Library Files which should not be an option as such but in this case I didn't have any choice to get it to work. So to make your code work follow the instruction below:
Go to ReaderDocument.m file and make the following changes:
+ (NSString *)documentsPath
{
//Make changes to return the NSBundle path.
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSFileManager *fileManager = [NSFileManager new]; // File manager instance
NSURL *pathURL = [fileManager URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:NULL];
// return [pathURL path]; // Path to the application's "~/Documents" directory // Code changes.
return bundlePath;
}
If you had a breakpoint just delete it, that solve it for me.
Breakpoint navigator => select the breakpoint then delete

Parsing a .csv file from a server with Objective-C

I have looked for an answer of a long time and still not found one so I thought I'd ask the question myself.
In my iPad app, I need to have the capability of parsing a .csv file in order to populate a table. I am using http://michael.stapelberg.de/cCSVParse to parse the csv files. However, I have only been successful in parsing local files. I have been trying to access a file from a server but am getting nowhere.
Here is my code to parse a local .csv file:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1)
{
//UITextField *reply = [alertView textFieldAtIndex:buttonIndex];
NSString *fileName = input.text;
NSLog(#"fileName %#", fileName);
CSVParser *parser = [CSVParser new];
if ([fileName length] != 0)
{
NSString *pathAsString = [[NSBundle mainBundle]pathForResource:fileName ofType:#"csv"];
NSLog(#"%#", pathAsString);
if (pathAsString != nil)
{
[parser openFile:pathAsString];
NSMutableArray *csvContent = [parser parseFile];
NSLog(#"%#", csvContent);
[parser closeFile];
NSMutableArray *heading = [csvContent objectAtIndex:0];
[csvContent removeObjectAtIndex:0];
NSLog(#"%#", heading);
AppDelegate *ap = [AppDelegate sharedAppDelegate];
NSManagedObjectContext *context = [ap managedObjectContext];
NSString *currentHeader = [heading objectAtIndex:0];
NSString *currentValueInfo = [heading objectAtIndex:1];
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:#"Field" inManagedObjectContext:context];
[newObject setValue:#"MIS" forKey:#"header"];
[newObject setValue:currentHeader forKey:#"fieldName"];
for (NSArray *current in csvContent)
{
NSManagedObject *newField = [NSEntityDescription insertNewObjectForEntityForName:#"Field" inManagedObjectContext:context];
[newField setValue:currentHeader forKey:#"header"];
[newField setValue:currentValueInfo forKey:#"valueInfo"];
NSLog(#"%#", [current objectAtIndex:0]);
[newField setValue:[current objectAtIndex:0] forKey:#"fieldName"];
[newField setValue:[NSNumber numberWithDouble:[[current objectAtIndex:1] doubleValue]] forKey:#"value"];
}
NSError *error;
if (![context save:&error])
{
NSLog(#"Couldn't save: %#", [error localizedDescription]);
}
[self storeArray];
[self.tableView reloadData];
}
}
}
input.text = nil;
}
Forgive the weird beginning and ending brace indentation. :/
Anyway, so that is my code to take input from a user and access a file locally which I'm sure you guys have realized already. Now I want to know how to get the path of a file in my server.
Also if you guys see anything else wrong such as writing style and other bad habits please tell me as I'm new to iOS.
Thank you so much in advance! If you didn't understand my question please clarify as I'm bad at explaining myself at times! :)
As I am guessing you are trying to get data from a server's .csv file and want to show that data in table view list.
so I suggest you try to get that .csv file data in NSData and then work on that.
NSData *responseData = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"serverUrl"]];
NSString *csvResponseString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] autorelease];
NSLog(#"responseString--->%#",csvResponseString);
Now try to use nsstring's method (componentsSeparatedByString) with coma (')
arrSepratedData = [[responseString componentsSeparatedByString:#","];
Now use this arr for UITableView data populate.

Can't get NSData into NSMutableArray

I have an array of objects that I want to save as a file and reload back into my app. It's saving the file (with some data inside) but I can't get it to read back into a NSMutable Array.
The objects are models that conform to the NSCoding protocol:
#implementation myModel
#synthesize name;
#synthesize number;
-(void) encodeWithCoder: (NSCoder *) encoder
{
[encoder encodeObject:name forKey:#"name"];
[encoder encodeInteger:number forKey:#"number"];
}
-(id) initWithCoder: (NSCoder *) decoder
{
name = [decoder decodeObjectForKey:#"name"];
number = [decoder decodeIntegerForKey:#"number"];
return self;
}
#end
So I create an array of these objects, then I save it...
- (void) saveMyOptions {
// Figure out where we're going to save the app's data files
NSString *directoryPath = [NSString stringWithFormat:#"%#/Library/Application Support/MyAppDir/", NSHomeDirectory()]; // points to application data folder for user
// Figure out if that directory exists or not
BOOL isDir;
NSFileManager *fileManager = [[NSFileManager alloc] init];
[fileManager fileExistsAtPath:directoryPath isDirectory:&isDir];
// If the directory doesn't exist, create it
if (!isDir)
{
[fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// Assemble everything into an array of objects with options
NSMutableArray *savedPreferences = [[NSMutableArray alloc] init];
myModel *saveOptions = nil;
for (int i; i < [otherArray count]; i++)
{
saveOptions = [[myModel alloc] init];
[saveOptions setName:#"Some String"];
[saveOptions setNumber:i];
[savedPreferences addObject:saveOptions];
saveOptions = nil;
}
// Actually save those options into a file
NSData* saveData = [NSKeyedArchiver archivedDataWithRootObject:savedPreferences];
NSString *fileName = [NSString stringWithFormat:#"%#filename.stuff", directoryPath];
NSError *error = nil;
BOOL written = [saveData writeToFile:fileName options:0 error:&error];
if (!written)
{
NSLog(#"Error writing file: %#", [error localizedDescription]);
}
}
So now I try to load that data back into an array. This is where I think it's falling apart...
- (NSMutableArray *) loadOptions {
// Create file manager object
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSData *saveData = nil;
// Find user directory path
NSString *directoryPath = [NSString stringWithFormat:#"%#/Library/Application Support/MyAppDir/", NSHomeDirectory()]; // points to application data folder for user
// Assign file name
NSString *fileName = [NSString stringWithFormat:#"%#filename.stuff", directoryPath];
// Create options array
NSMutableArray *myOptions = nil;
// If the file exists, fill the array with options
if ([fileManager fileExistsAtPath:fileName])
{
saveData = [NSData dataWithContentsOfFile:fileName];
myOptions = [NSKeyedUnarchiver unarchiveObjectWithData:saveData];
}
NSLog(#"%lu", [myOptions count]); // This ALWAYS reports 0!
NSLog(#"%lu", [saveData length]); // This reports a value of 236;
return myOptions;
}
Could someone point me in the direction of where I'm going wrong? I'm throughly confused :-(
Thanks in advance!
You are missing the super calls in your encodeWithCoder: and initWithCoder: methods, but that's just a guess. Why not use NSUserDefaults for saving preferences?
You might also want to make sure that your objects are retained is set using the synthesized setter.
- (void)encodeWithCoder:(NSCoder *)encoder {
[super encodeWithCoder:encoder];
[encoder encodeObject:name forKey:#"name"];
[encoder encodeInteger:number forKey:#"number"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
self.name = [decoder decodeObjectForKey:#"name"];
self.number = [decoder decodeIntegerForKey:#"number"];
}
return self;
}
For your info, NSKeyedArchiver also has a method you can use directly to operate on files:
+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path
and NSKeyedUnarchiver:
+ (id)unarchiveObjectWithFile:(NSString *)path

ObC : app crashes after returning NSMutableArray?

I am new to ObC and have a problem that i just cant fix. There may be other issues as well but the main issue is this:
Starting the app
Press button = load new view
In the new viewDidLoad i call another object/function and send a NSMutableArray
Process data and send back a NSMutableArray
App crash, see comment where. Most often when i go back and back again but sometimes the first time
As i am new to this i guess i do a lot of this wrong but could someone nice take a look at the code and give me some advice. I would assume i have problem with releasing something.
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#" ");
NSLog(#"viewDidLoad ");
NSLog(#" ");
NSLog(#">>Processing prepareGame<<");
NSMutableArray *propArray1 = [[NSMutableArray alloc] initWithObjects:#"9999", nil]; //Init with dummy numbers
AccessPropertiesFile *readMyProperties = [AccessPropertiesFile new]; //Init function call to read file
NSLog(#"Prepare to call readProperties");
propArray1 = [readMyProperties readPropertiesFile:propArray1];
NSLog(#"Back from readProperties:error after this");
/*
for (NSString *element in propArray1) {
NSLog(#"Elements in prop2Array; %#", element);
}
*/
[readMyProperties release];
[propArray1 release];
}
-(NSMutableArray *)readPropertiesFile:(NSMutableArray *)readDataArray {
NSLog(#"Processing readProperties");
// For error information
NSError *error;
//Prepare File Manager
NSString *filePath = [self dataFilePath];
NSFileManager *fileMgr;
fileMgr = [NSFileManager defaultManager];
NSArray *propertiesArray = [NSArray alloc]; //Alloc array
//Check from what module the call is coming from to ecide what to do
if ([fileMgr fileExistsAtPath: filePath] == NO) {
NSLog (#"File not found");
//File does not exists, this is the first time the game starts
//Set up default parameters
NSString *fileString =#"0\n30\n30\n10\n1\n1\n1\n2\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n";
// Write default parameters to file
[fileString writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
propertiesArray = [fileString componentsSeparatedByString:#"\n"]; // each line, adjust character for line endings
}
else { //File exists
NSLog (#"File exists");
NSString *fileString = [NSString stringWithContentsOfFile:filePath
encoding:NSUTF8StringEncoding error:nil]; // reads file into memory as an NSString
propertiesArray = [fileString componentsSeparatedByString:#"\n"]; // each line, adjust character for line endings
}
//Clean readDataArray
[readDataArray removeAllObjects];
//Populate return array
for (NSString *element in propertiesArray) {
//NSLog(#"Elements in propertiesArray; %#", element);
[readDataArray addObject:element];
}
NSLog(#"readDataArray: %#", readDataArray);
[propertiesArray release];
[readDataArray autorelease];
NSLog(#"returning from readProperties");
return readDataArray;
}
#end
You are over-releasing readDataArray (known as propArray1 in the method that didn't create it). You create it and autorelease it in your second method, then you release it again at the end of your first method (where it wasn't created).
I suggest you use Analyze feature that comes with latest XCode. It is a good feature that I always use to track if I forget to release or release too much.
I also spotted that you also over-release the propertiesArray because it contains the result from [fileString componentsSeparatedByString:], which will be autorelease according to Cocoa convention.

Error while checking if a file exists

I'm trying to write to a plist file using writeToFile, before I write I check whether the file exists.
This is the code:
#import "WindowController.h"
#implementation WindowController
#synthesize contacts;
NSString *filePath;
NSFileManager *fileManager;
- (IBAction)addContactAction:(id)sender {
NSDictionary *dict =[NSDictionary dictionaryWithObjectsAndKeys:
[txtFirstName stringValue], #"firstName",
[txtLastName stringValue], #"lastName",
[txtPhoneNumber stringValue], #"phoneNumber",
nil];
[arrayContacts addObject:dict];
[self updateFile];
}
- (void)awakeFromNib {
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
filePath = [rootPath stringByAppendingPathComponent:#"Contacts.plist"];
fileManager = [NSFileManager defaultManager];
contacts = [[NSMutableArray alloc] init];
if ([fileManager fileExistsAtPath:filePath]) {
NSMutableArray *contactsFile = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
for (id contact in contactsFile) {
[arrayContacts addObject:contact];
}
}
}
- (void) updateFile {
if ( ![fileManager fileExistsAtPath:filePath] || [fileManager isWritableFileAtPath:filePath]) {
[[arrayContacts arrangedObjects] writeToFile:filePath atomically:YES];
}
}
#end
When the addContactAction is executed I don't get any error but the program halts and it brings me to the debugger. When I press continue in the debugger I get:
Program received signal: “EXC_BAD_ACCESS”.
But that's probably not important.
PS: I'm new to mac programming and I don't know what else to try since I don't get an error message that tells me what's going wrong.
The path to the file is:
/Users/andre/Documents/Contacts.plist
I earlier tried this(with the same result), but I read that you can only write to the documents folder:
/Users/andre/Desktop/NN/NSTableView/build/Debug/NSTableView.app/Contents/Resources/Contacts.plist
Does anyone have an idea or even an explanation why this happens?
First, I think you shouldn't instantiate an NSFileManager object. Instead you use the default file manager, like this:
[[NSFileManager defaultManager] fileExistsAtPath: filePath];
Then, could you specify at which line the program is breaking into the debugger?
You are setting filePath with the stringByAppendingPathComponent: method. That method returns an autoreleased object. (Autoreleased object is used after it has been (automatically) released, which could cause the bad access error.)
I think changing
[rootPath stringByAppendingPathComponent:#"Contacts.plist"];
into
[[rootPath stringByAppendingPathComponent:#"Contacts.plist"] retain];
will solve your troubles.