Saving files in cocoa - file

I'm sure this is a really easy to answer question but I'm still new to cocoa. I need to save my applications data. The app has 4 text fields and each field needs to be saved into one file. Then when you open the file it needs to know what goes in what field. I'm really stuck with this. Also, I do know how to use the save panel.

A convenient way would be to use PLists:
NSDictionary *arr = [NSDictionary dictionaryWithObjectsAndKeys:
string1, #"Field1", string2, #"Field2", nil];
NSData *data = [NSPropertyListSerialization dataFromPropertyList:arr
format:NSPropertyListXMLFormat_v1_0 errorDescription:nil];
NSSavePanel *panel = [NSSavePanel savePanel];
NSInteger ret = [panel runModal];
if (ret == NSFileHandlingPanelOKButton) {
[data writeToURL:[panel URL] atomically:YES];
}
For deserialization:
NSData *data = [NSData dataWithContentsOfURL:urlOfFile];
NSDictionary *dict = [NSPropertyListSerialization propertyListFromData:data
mutabilityOption:NSPropertyListImmutable
format:nil errorDescription:nil];
NSString *string1 = [dict objectForKey:#"Field1"];
// ... etc.

Related

Objective-C - How to write an array to a file?

I have a text file that I can scan (NSScanner) and tag. The results are stored in an array. The structure is two strings, one for english and one for greek.
I want to write an output file that maintains the array structure. I presume I can create a plist file for this purpose.
However, I'm stuck and need help to create this file.
I have a test whether the file was created, but I get this result:
outgoingWords.count: 442
2014-08-12 17:54:17.369 MyScanner[97350:2681695] *** Assertion failure in -[ViewController checkArray], /Users/david/Desktop/Word Scanning/MyScanner/MyScanner/ViewController.m:98
2014-08-12 17:54:17.373 MyScanner[97350:2681695] Failed to set (contentViewController) user defined inspected property on (NSWindow): writeToFile failed
The code I'm using so far is as follows:
-(void)checkArray {
//do stuff to verify the array
long i = outgoingWords.count;
NSString *tempString = [NSString stringWithFormat:#"%li", i];
NSLog(#"outgoingWords.count: %#", tempString); //442
NSArray *tempArray2 = [outgoingWords copy];
NSString *path = [[NSBundle mainBundle] pathForResource:#"/Users/David/Desktop/alfa2" ofType:#"plist"];
BOOL success = [tempArray2 writeToFile:path atomically:YES];
NSAssert(success, #"writeToFile failed");
}
Could someone either identify what I'm missing, or point me to an existing answer I can use (I've looked)..
Many thanks..
edit. I've also tried the approach in this SO question. But get the same result.
NSString *path = [[NSBundle mainBundle] pathForResource:#"/Users/David/Desktop/alfa2" ofType:#"plist"];
NSString *error;
NSData *data = [NSPropertyListSerialization dataFromPropertyList:tempArray2 format:NSPropertyListBinaryFormat_v1_0 errorDescription:&error];
BOOL success = [NSKeyedArchiver archiveRootObject:data toFile:path];
NSAssert(success, #"archiveRootObject failed");
Problem is that you are writing to the bundle, which is not allowed. You should write to a path in your document or other directory, for example:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
filePath = [documentsDirectory stringByAppendingPathComponent:#"FileName.xxx"];
[data writeToFile:filePath atomically:NO];

Best way to populate this array using .txt file?

I'm looking to make SEVERAL arrays populated with words. Eventually I want to be able to just pull a random word from the array and display it (I've mastered that). What I'm wondering is what is the best way to populate this array. Should I just type in:
[NSArray arrayWithObjects:#"word1",#"word2",#"word3",#"word4",#"word5",nil]
or is there a better way where the words are stored in a .txt file and I can just have a loop add each word in the text file to the Array?
I'm looking at filling the arrays with 100's of words. Any and all help is appreciated :D.
UPDATE
After doing some research I found this here. It seems to be exactly what I wanted. The only thing is it gives me a warning
'stringWithContentsOfFile' is deprecated
I know the full NSString method is:
stringWithContentsOfFile:(NSString *) encoding:(NSStringEncoding) error:(NSError **)
But I don't know what to put for encoding (and I'm assuming I can just put 'nil' for NSError). Other than that it works like a charm. I might consider switching from paths to urls. Here is the code that I found:
- (NSArray *) readFile:(NSString *) path {
NSString *file = [NSString stringWithContentsOfFile:path];
NSMutableArray *dict = [[NSMutableArray alloc] init];
NSCharacterSet *cs = [NSCharacterSet newlineCharacterSet];
NSScanner *scanner = [NSScanner scannerWithString:file];
NSString *line;
while(![scanner isAtEnd]) {
if([scanner scanUpToCharactersFromSet:cs intoString:&line]) {
NSString *copy = [NSString stringWithString:line];
[dict addObject:copy];
}
}
NSArray *newArray = [[NSArray alloc] initWithArray:dict];
return newArray;
}
You can probably use NSUTF8StringEncoding for the encoding parameter (depending on how you created the file, but this is the most common).
Instead of using NSScanner, you can also simply split the string into lines with the componentsSeparatedByString: method. This reduces your method to just these two lines:
NSString *file = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
return [file componentsSeparatedByString:#"\n"];
Btw, you shouldn't name an array variable "dict", this would imply that it's an NS(Mutable)Dictionary.
The less code way to get in a list of words would be:
NSString *path = [[NSBundle mainBundle] pathForResource:#"Words" ofType:#"plist"];
NSArray *words = [[NSArray alloc] initWithContentsOfFile:path];
Then you just have to create a plist that has all the words in it with the root object as an array.
In the method you have above the encoding you are looking for is NSUTF8StringEncoding and you actually should pass an NSError by reference, if something goes wrong the error could be useful:
NSError *anError = nil;
NSString *file = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&anError];

MapKit based app crashing when loading from plist

I'm writing a program which uses MapKit to display a map which will load custom annotations from a plist file. Each annotation is a dictionary item in the root array, with a title, subtitle, latitude, and longitude. When I hard-coded in the annotations for test purposes, the program worked beautifully. But with the addition of the MapDemoAnnotation class and my attempt to read in the property list, the program crashes upon launch.
Here is my annotation implementation:
#import "MapDemoAnnotation.h"
#implementation MapDemoAnnotation
#synthesize coordinate;
#synthesize title;
#synthesize subtitle;
-(id)initWithDictionary:(NSDictionary *)dict{
self = [super init];
if(self!=nil){
coordinate.latitude = [[dict objectForKey:#"latitude"] doubleValue];
coordinate.longitude = [[dict objectForKey:#"longitude"] doubleValue];
self.title = [dict objectForKey:#"name"];
self.subtitle = [dict objectForKey:#"desc"];
}
return self;
}
-(void)dealloc{
[title release];
[subtitle release];
[super dealloc];
}
#end
I'm guessing the viewDidLoad method in my RootViewController class is the problem, though.
- (void)viewDidLoad {
[super viewDidLoad];
MKMapView *mapView = (MKMapView*)self.view;
mapView.delegate = self;
mapView.mapType=MKMapTypeHybrid;
CLLocationCoordinate2D coordinate;
coordinate.latitude = 39.980283;
coordinate.longitude = -75.157568;
mapView.region = MKCoordinateRegionMakeWithDistance(coordinate, 2000, 2000);
//All the previous code worked fine, until I added the following...
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"Locations" ofType:#"plist"];
NSData* data = [NSData dataWithContentsOfFile:plistPath];
NSMutableArray* array = [NSPropertyListSerialization propertyListFromData:data
mutabilityOption:NSPropertyListImmutable
format:NSPropertyListXMLFormat_v1_0
errorDescription:nil];
if (array) {
NSMutableDictionary* myDict = [NSMutableDictionary dictionaryWithCapacity:[array count]];
for (NSDictionary* dict in array) {
MapDemoAnnotation* annotation = [[MapDemoAnnotation alloc]initWithDictionary:dict];
[mapView addAnnotation:annotation];
[annotation release];
}
NSLog(#"The count: %i", [myDict count]);
}
else {
NSLog(#"Plist does not exist");
}}
The program crashes for reasons I cannot figure, but I figure I must have done something wrong in reading in the property list or else in the MapDemoAnnotation class. Am I missing something obvious, or making a novice mistake?
My code is largely borrowed, so I could be way off base with how I'm approaching it.
Thanks in advance!
The third parameter in the call to propertyListFromData is wrong. The compiler must be giving you a "makes pointer from integer without a cast" warning there because the format parameter expects a pointer to a NSPropertyListFormat variable (so the method can return the format to you). So you need to do:
NSPropertyListFormat propertyListFormat;
NSMutableArray* array = [NSPropertyListSerialization
propertyListFromData:data
mutabilityOption:NSPropertyListImmutable
format:&propertyListFormat
errorDescription:nil];
However, the documentation mentions that the above method is obsolete and you should use propertyListWithData:options:format:error: instead.
However, it's much easier to just call NSArray's initWithContentsOfFile: method instead:
NSString *plistPath = [[NSBundle mainBundle] pathForResource...
NSArray *array = [[NSArray alloc] initWithContentsOfFile:plistPath];
if (array) {
//your existing code here...
}
else {
NSLog(#"Plist does not exist");
}
[array release];

Insert Array in SQLite database?

Okay,
I am a noob and want to get a array from my server and insert it into a SQLite database table. My code is below. Please help!
- (void)getAlbums {
// Get Albums
NSString *userId;
userId = #"1";
NSString *post =[NSString stringWithFormat:#"userid=%#", userId];
NSString *hostStr = #"********************************************?";
hostStr = [hostStr stringByAppendingString:post];
NSData *dataURL = [NSData dataWithContentsOfURL: [ NSURL URLWithString: hostStr ]];
NSString *serverOutput = [[NSString alloc] initWithData:dataURL encoding: NSASCIIStringEncoding];
//Create Array
NSArray *myWords = [serverOutput componentsSeparatedByString:#" "];
// Output server response
NSLog(serverOutput);
//Initialize the array.
NSMutableArray *listOfItems = [[NSMutableArray alloc] init];
listOfItems = [[NSArray arrayWithObjects: myWords, nil] retain];
NSString *test;
while (*test in listOfItems) {
[sqlite executeNonQuery:#"INSERT INTO photo_albums VALUES (?, ?);", variableOne, variableTwo];
}
}
there is more than one issue in this code.
NSMutableArray *listOfItems = [[NSMutableArray alloc] init];
this will leak in the next line because you reassign another item to the variable. Change it into:
NSArray *listOfItems;
listOfItems = [[NSArray arrayWithObjects: myWords, nil] retain];
this does not what you think it does. It will initialize an array with the array myWords as a "member", it won't add the objects from myWords.
But why does this line exist anyway? myWords is already an array. And the retain which doesn't get released would be another leak.
NSString *test;
while (*test in listOfItems) {
this is just wrong. use:
for (test in listOfItems) {
[sqlite executeNonQuery:#"INSERT INTO photo_albums VALUES (?, ?);", variableOne, variableTwo];
where the f do you get variableOne and variableTwo from? And why do you loop through your listOfItems if you don't use the NSString named test?
}
no problem with the closing bracket.
Oh and serverOutput doesn't get released too, so there is another leak. And I'm pretty sure that the encoding isn't ascii.
Start over with the iOS 101, learn the basics and ignore sqlite for the next 2 weeks.

The easiest way to write NSData to a file

NSData *data;
data = [self fillInSomeStrangeBytes];
My question is now how I can write this data on the easiest way to an file.
(I've already an NSURL file://localhost/Users/Coding/Library/Application%20Support/App/file.strangebytes)
NSData has a method called writeToURL:atomically: that does exactly what you want to do. Look in the documentation for NSData to see how to use it.
Notice that writing NSData into a file is an IO operation that may block the main thread. Especially if the data object is large.
Therefore it is advised to perform this on a background thread, the easiest way would be to use GCD as follows:
// Use GCD's background queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Generate the file path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:#"yourfilename.dat"];
// Save it into file system
[data writeToFile:dataPath atomically:YES];
});
writeToURL:atomically: or writeToFile:atomically: if you have a filename instead of a URL.
You also have writeToFile:options:error: or writeToURL:options:error: which can report error codes in case the saving of the NSData failed for any reason. For example:
NSError *error;
NSURL *folder = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:true error:&error];
if (!folder) {
NSLog(#"%s: %#", __FUNCTION__, error); // handle error however you would like
return;
}
NSURL *fileURL = [folder URLByAppendingPathComponent:filename];
BOOL success = [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
if (!success) {
NSLog(#"%s: %#", __FUNCTION__, error); // handle error however you would like
return;
}
The easiest way, if you want to do this a few times manually instead of coding, is to only use your mouse:
Put a breakpoint one line after your NSdata are fulfilled.
Hold your mouse over your NSData variable, click on the Eye button, and then export.
After that chose the storage details (name/extension/location of the file).
Click on "Save" and you are done!

Resources