Format contents of NSMutableArray for .csv file - ios

I have a picture slideshow app which allows the user to rate 3 images. The ratings get stored in a NSMutableArray called rated like this:
2014-01-06 07:10:23.040 SlideShowSurvey[50425:70b] (
1, <-- Rating for Picture 1
2, <-- Rating for Picture 2
3 <-- Rating for Picture 3
)
This is then saved to a .csv file using the following code:
-(void)saveRatings
{
NSString *picRatings = [NSString stringWithFormat:#"%#, \n",self.rated];
// Find documents directory
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *survey = [docPath stringByAppendingPathComponent:#"pictureRatings.csv"];
// Create new file if none exists
if (![[NSFileManager defaultManager] fileExistsAtPath:survey])
{
[[NSFileManager defaultManager] createFileAtPath:survey contents:nil attributes:nil];
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:survey];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[picRatings dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
}
I can then display the results of the .csv file to a UITextView, though it displays it exactly how the array is structured. For example, multiple results display as:
(
1,
2,
3
),
(
1,
2,
3
),
(
1,
2,
3
)
Is there a way I am able to format the array so that it is saved as 1,2,3? And would I be able to add a column header like Picture 1, Picture 2, Picture 3? For example, I would like to display results on the UITextView something like:
Picture 1, Picture 2, Picture 3
1,2,3
1,2,3
1,2,3
I've tried searching but can't seem to find this answer. Any help is appreciated! Thanks.
Edit: I have solved this thanks to jrturton's solution, using the following code:
// Set column titles for .csv
NSArray *columnTitleArray = #[#"Picture 1", #"Picture 2", #"Picture 3"];
NSString *columnTitle = [columnTitleArray componentsJoinedByString:#","];
NSString *columnTitleToWrite = [columnTitle stringByAppendingString:#"\n"];
// Separate ratings into cells
NSString *picRatings = [rated componentsJoinedByString:#","];
NSString *picRatingsToWrite = [picRatings stringByAppendingString:#"\n"];
// Find documents directory
..
Then adding this to the method to make sure column headers are only set when a new file is created:
// Create new file if none exists
if (![[NSFileManager defaultManager] fileExistsAtPath:survey])
{
[[NSFileManager defaultManager] createFileAtPath:survey contents:nil attributes:nil];
// Set title for new file
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:survey];
[fileHandle writeData:[columnTitleToWrite dataUsingEncoding:NSUTF8StringEncoding]];
}
..

Here is a much easier way:
NSMutableString *csv = [NSMutableString string];
NSString *label = ...;
[csv appendFormat:#"%#,\n", label];
NSNumber *keyNum = ...;
[csv appendFormat:#"%d,%d\n", [keyNum intValue], [countNum intValue]];
NSString *filename = #"counts.csv";
NSError *error;
[csv writeToFile:filename atomically:YES encoding:NSUTF8StringEncoding error:&error];

NSMutableString *csv = [NSMutableString string];
NSString *label = #"\n";
[csv appendFormat:#",", label];
NSString *picRatings = [rated componentsJoinedByString:csv];
I'm not sure what you expect appendFormat to be doing in the above code, this will probably be raising a warning since label is not used. You don't need a mutable string in this case either.
To get the contents of an array separated by commas, do this:
NSString *picRatings = [rated componentsJoinedByString:#","];
You also need to add a new line to the end of this string, so you'd want:
NSString *picRatingsToWrite = [picRatings stringByAppendingString:#"\n"];
Write this string to your file and you should be fine.

use this parser and get the data into array and look how you can use data into UITabelViewCell https://github.com/davedelong/CHCSVParser

Related

Add Header to CSV file

Part of my iOS app will include exporting a .csv file.
I have the file made and data is added to each row as I would like.
I would like to add a header row so if a file is emailed to a person they will know what each column is for.
How do I implement something like this?
Here is my csv code below:
- (NSString *)dataFilePath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:#"myfile.csv"];
}
- (IBAction)save:(id)sender
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[self dataFilePath]]) {
[[NSFileManager defaultManager] createFileAtPath: [self dataFilePath] contents:nil attributes:nil];
}
NSString * writeString = [NSString stringWithFormat:#"%#,%#,%#\n", self.nameTextField.text, self.cityTextField.text, self.stateTextField.text];
NSFileHandle *handle;
handle = [NSFileHandle fileHandleForWritingAtPath: [self dataFilePath]];
[handle truncateFileAtOffset:[handle seekToEndOfFile]];
[handle writeData:[writeString dataUsingEncoding:NSUTF8StringEncoding]];
self.nameTextField.text = #"";
self.cityTextField.text = #"";
self.stateTextField.text = #"";
[self.stateTextField resignFirstResponder];
}
Questions 2:
How can I write over a row? Currently if I update any of the UITextFields a new row is added.
A header row in a CSV file is simply fixed data in the first row - it is up to the program that reads the CSV file to treat the first row as a header row - Excel has an option in the file open/import dialog, for example.
In your code you can simply write the header when you first create the file -
- (IBAction)save:(id)sender
{
NSString *headerRow;
if (![[NSFileManager defaultManager] fileExistsAtPath:[self dataFilePath]]) {
[[NSFileManager defaultManager] createFileAtPath: [self dataFilePath] contents:nil attributes:nil];
headerRow=#"name,city,state";
}
NSString * writeString = [NSString stringWithFormat:#"%#,%#,%#\n", self.nameTextField.text, self.cityTextField.text, self.stateTextField.text];
NSFileHandle *handle;
handle = [NSFileHandle fileHandleForWritingAtPath: [self dataFilePath]];
[handle truncateFileAtOffset:[handle seekToEndOfFile]];
if (headerRow != nil) {
[handle writeData:[headerRow dataUsingEncoding:NSUTF8StringEncoding]];
}
[handle writeData:[writeString dataUsingEncoding:NSUTF8StringEncoding]];
self.nameTextField.text = #"";
self.cityTextField.text = #"";
self.stateTextField.text = #"";
[self.stateTextField resignFirstResponder];
}
Right away you need to know that adding something to a CSV file makes it not a CSV file. Exporting with comments will work if you're also the only one importing it - in which case it is your custom data format based on CSV. There is a defined IETF standard for CSV - RFC4180.
To make it work you would need to define some escape character that informs your format parser that a line is a comment. I suggest "", - this will never normally appear in a RFC4180-compliant CSV file.
In practical terms, add your comment lines after each CSV-compliant line. In your save: method you have only one line so it will work to write a comment line and then the RFC-compliant line or in the opposite order, as long as you preserve the integrity of the RFC lines. If you ever want to save a longer string with many lines, you would need to split the NSString you are saving with something like
- (NSArray *)componentsSeparatedByString:(NSString *)separator
where your separator is a "\n", and then loop through the resulting array to write each line, adding your comments as required.
You can use a regular CSV parser still (like CHCSVParser) if you preprocess the file and strip out the lines beginning with your special comment marker. Again you would need to split the incoming file by line break and discard the comments before trying to parse it.
THANK YOU Dave DeLong for CHCSVParser! I use it often.

How to insert data to text file with using NSFileHandle

My code replaces text instead of inserting it starting from 5 symbol:
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath: filePath];
[file seekToFileOffset: 5];
[file writeData: [value dataUsingEncoding:NSUTF8StringEncoding]];
Is there any way to insert data to text file?
That's because your code set the index position to 5 and start writing from there, thus replacing everything from 5 onwards.
I would copy the content of the file to a variable and modify it from there as a string.
as by the looks of it what you attempt to do is not possible
Update:
Given that what you need is to write from X offset this should do the trick
NSFileHandle *file;
NSMutableData *data;
const char *bytestring = "black dog";
data = [NSMutableData dataWithBytes:bytestring length:strlen(bytestring)];
file = [NSFileHandle fileHandleForUpdatingAtPath: #"/tmp/quickfox.txt"];
if (file == nil)
NSLog(#"Failed to open file");
[file seekToFileOffset: 10];
[file writeData: data];
[file closeFile];
Well this might not be an efficient way, but you could read the entire text file into an NSMutableString and then use insertString:atIndex: and then write it back out. As far as I know there is no way to insert text into an existing file. Similar question
A quick example:
NSString *path = //Your file
NSMutableString *contents = [NSMutableString stringWithContentsOfFile:txtFilePath encoding:NSUTF8StringEncoding error:NULL];
[contents insertString:#"Some string to insert" atIndex:5];
[contents writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:NULL];
Inserting data somewhere in a sequential file would require that the entire file be rewritten. It is possible, however, to add data to the end of a file without rewriting the file.

saving text to a file inside app [duplicate]

This question already has answers here:
Writing and reading text files on the iPhone
(2 answers)
Closed 9 years ago.
Right, I've seen solutions like this before, but nine that seem to do the trick. I want to save text from a Text Field into a file in Supporting Files.
Preferably, I want it to be an HTML file, and I want it there because I want to open it again in a Web View.
I know how to do the loading, its just the saving.
The question: How do I save text from a Text Field into a file inside the Supporting Files directory?
Try something like this:
- (void)writeStringToFile:(NSString*)aString {
// Build the path, and create if needed.
NSString *theString = #"The text to write";
NSString* filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* fileAtPath = [filePath stringByAppendingPathComponent:#"data.txt"];
if (![[NSFileManager defaultManager] fileExistsAtPath:fileAtPath]) {
[[NSFileManager defaultManager] createFileAtPath:fileAtPath contents:nil attributes:nil];
}
[[theString dataUsingEncoding:NSUTF8StringEncoding] writeToFile:fileAtPath atomically:NO];
}
Problem Solved (HTML) !!!
try this code,
NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0];
NSString *fileName = [documentDirectory stringByAppendingPathComponent:#"myfile.html"];
NSString *textFromTextField = textField.text;
NSString *finalText = [NSString stringWithFormat:#"<html><body>%#</body><html>",textFromTextField];
[finalText writeToFile:#"" atomically:YES encoding:NSUTF8StringEncoding error:NULL];

Parsing and processing Text Strings in iOS

Wanted to find the best programming approach in iOS to manipulate and process text strings. Thanks!
Would like to take a file with strings to manipulate the characters similar to the following:
NQXB26JT1RKLP9VHarren Daggett B0BMAF00SSQ ME03B98TBAA8D
NBQB25KT1RKLP05Billison Whiner X0AMAF00UWE 8E21B98TBAF8W
...
...
...
Each string would process in series then loop to the next string, etc.
Strip out the name and the following strings:
Take the following 3 string fragments and convert to another number base. Have the code to process the new result but unsure of how to send these short strings to be processed in series.
QXB26
B0BM
BAA8
Then output the results to a file. The xxx represents the converted numbers.
xxxxxxxxx Harren Daggett xxxxxxxx xxxxxxxx
xxxxxxxxx Billison Whiner xxxxxxxx xxxxxxxx
...
...
...
The end result would be pulling parts of strings out of the first file and create a new file with the desired result.
There are several ways to accomplish what you are after, but if you want something simple and reasonably easy to debug, you could simply split up each record by the fixed position of each of the fields you have identified (the numbers, the name), then use a simple regular expression replace to condense the name and put it all back together.
For purposes like this I prefer a simple (and even a bit pedestrian) solution that is easy to follow and debug, so this example is not optimised:
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *URLs = [fm URLsForDirectory: NSDocumentDirectory
inDomains: NSUserDomainMask];
NSURL *workingdirURL = URLs.lastObject;
NSURL *inputFileURL = [workingdirURL URLByAppendingPathComponent:#"input.txt" isDirectory:NO];
NSURL *outputFileURL = [workingdirURL URLByAppendingPathComponent:#"output.txt" isDirectory:NO];
// For the purpose of this example, just read it all in one chunk
NSError *error;
NSString *stringFromFileAtURL = [[NSString alloc]
initWithContentsOfURL:inputFileURL
encoding:NSUTF8StringEncoding
error:&error];
if ( !stringFromFileAtURL) {
// Error, do something more intelligent that just returning
return;
}
NSArray *records = [stringFromFileAtURL componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]];
NSMutableArray *newRecords = [NSMutableArray array];
for (NSString *record in records) {
NSString *firstNumberString = [record substringWithRange:NSMakeRange(1, 5)];
NSString *nameString = [record substringWithRange:NSMakeRange(15, 27)];
NSString *secondNumberString = [record substringWithRange:NSMakeRange(43, 4)];
NSString *thirdNumberString = [record substringWithRange:NSMakeRange(65, 4)];
NSString *condensedNameString = [nameString stringByReplacingOccurrencesOfString:#" +"
withString:#" "
options:NSRegularExpressionSearch
range:NSMakeRange(0, nameString.length)];
NSString *newRecord = [NSString stringWithFormat: #"%# %# %# %#",
convertNumberString(firstNumberString),
condensedNameString,
convertNumberString(secondNumberString),
convertNumberString(thirdNumberString) ];
[newRecords addObject: newRecord];
}
NSString *outputString = [newRecords componentsJoinedByString:#"\n"];
[outputString writeToURL: outputFileURL
atomically: YES
encoding: NSUTF8StringEncoding
error: &error];
In this example convertNumberString is a plain C function that converts your number strings. It could of course also be a method, depending on the architecture or your preferences.

IOS: problem to synchronize nsarray with string

I have a NSArray in this way
myArray[0] = [string1, string2, string3, string4, mySecondArray, string5]; (at 0 position)
I write this array inside a txt file in this way
NSString *outputString = #"";
for (int i = 0; i< myArray.count; i++){
outputString = [outputString stringByAppendingString:[[[myArray objectAtIndex:i ] componentsJoinedByString:#"#"] stringByAppendingString:#";"]];
}
NSLog(#"string to write = %#", outputString);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"Text.txt"];
NSError *error;
[outputString writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
then the result of NSLog is = (position 0 of myArray) (mySecond array is empty)
one#two#three#four#(
)#five;
I want to know:
Why the array wrap?
When I'll go to read this string how can I know that it's mySecondArray?
When you message componentsJoinedByString: on an NSArray object, it calls description on each of its objects and concatenates them in order. For NSString objects, they are the strings themselves. The array wraps because of the way the description method has been implemented.
As for identifying the array while you are reading the string back, I don't think it is possible. You should consider writing the array to the file rather i.e.
[[myArray objectAtIndex:0] writeToFile:filePath atomically:YES];
or
[myArray writeToFile:filePath atomically:YES];
depending on the requirement. This way you will be able to read the elements back properly.

Resources