iOS: Create a dynamic registration form - ios

I am working on a registration form app. Right now I'm trying to come up with a way to to dynamically create forms. Right now I have a hardcoded form, with hardcoded questions, that will save the form data into a .csv file. Here is some of the code:
// saves text field data in comma separated CSV file format
NSString *formData = [NSString stringWithFormat:#"%#,%#,%#,%#,%#,%#,%#,%#,%#,%#,%#\n",
self.nameTextfield.text, self.emailTextfield.text,
self.phoneTextfield.text, self.termTextfield.text,
self.schoolTextfield.text, self.graduationTextfield.text,
self.gpaTextfield.text, degreeString,
self.interestTextfield.text, groupString,
appliedString];
// get document directory path
NSString *documentDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES)objectAtIndex:0];
NSString *csv = #".csv";
NSString *filename = [NSString stringWithFormat:#"%#%#", _eventName.text, csv];
// append results.csv onto doc path
NSString *event = [documentDirectoryPath stringByAppendingPathComponent:filename];
// creates folder if it does not exist
if (![[NSFileManager defaultManager] fileExistsAtPath:event]) {
[[NSFileManager defaultManager] createFileAtPath:event contents:nil attributes:nil];
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:event];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[formData dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
Basically I want to be able to create a form on the app that can be any number of questions, and either uses segmented controls or text inputs. One issue I see already is formatting the file in the comma separated format. For instance, if the app creates 5 questions, how do you format the file such that formData = [NSString stringWithFormat:#"%#,%#,%#,%#,%#\n"] without hardcoding that in?
Thank you so much for your help!

One (but not the only) way to correctly create dynamic form is to build it of typical blocks (such as switch-block, textfield-block, etc). You can add these blocks to view manually or use UITableView for example. Also you need data structure describing concrete form (number and types of blocks).
Formatting file is no problem since you always can use NSMutableString and append values one by one.

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.

CSV to NSString results in empty null object

I'm trying to retrieve content of a csv file to NSString. Thats what I do:
NSString *strBundle = [[NSBundle mainBundle] pathForResource:#"socs" ofType:#"csv"];
NSLog(#"bundle path: %#",strBundle);
NSString *file = [NSString stringWithContentsOfFile:strBundle
encoding:NSUTF8StringEncoding
error:nil];
if ([[NSFileManager defaultManager]fileExistsAtPath:strBundle]) {
NSLog(#"file is there!!!");
}else {
NSLog(#"no file");
}
NSLog(#"file: %#",file);
NSArray *allLines = [file componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSLog(#"lines: %lu",(unsigned long)[allLines count]);
file manager shows that the file is there. When i try to log the NSString or number of files it says null. I even created NSData object with the content of exactly the same file and when I logged the NSData object, I clearly saw that there is some data. Then when I tried to create NSString with the content of NSData, I had the same result as before - null. Maybe the problem is somewhere in the formatting of the file?
Any help will be appreciated :)
I see 3 issues:
You are passing a nil argument to the error: parameter in your stringWithContentsOfFile: line. If there's a possibility something might go wrong (and apparently there is), you should pass a real argument there so you can figure out what went wrong.
You can use componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet], but that has a tendency to produce blank "components" between every line. Plain old #"\n" works better in virtually all cases I've run into.
You should be checking fileExistsAtPath before you try to load it into the NSString
If you were truly able to create an NSData object from the path it doesn't necessarily mean it's correct data. But let's say it is, if you were not able to convert it to a NSString then you need to check your encoding parameter.

Save contents of NSMutableArray to file [duplicate]

This question already has an answer here:
Writing a NSArray to file
(1 answer)
Closed 9 years ago.
I have an app which has several ViewControllers. Each ViewController has a question with 2-5 buttons as possible answers. The title of a button is saved to a NSMutableArray called submission. The user can go back to previous questions, change their answer, and the NSMutableArray will be updated accordingly. I need to save this array to file so new results can be saved into a UITableView each time the questionnaire is completed. I have researched and it sounds like a .plist is a good option, as all of my objects in the array are NSStrings.
A sample of my array:
2013-12-17 14:06:34.210 Questionnaire[1724:70b] (
1234,
"Dec 17, 2013",
Yes,
Games,
"Not Applicable",
Yes
)
"1234" is the User ID, the date is the Date of Birth, and the other submissions are the answers to each question.
My ViewControllers look like this:
MainViewController
InfoViewController <-- Array allocated + initialised, inserting ID and DoB
Q1ViewController <-- question
..
Q4ViewController <--question
ENDViewController <-- offers user options for Home or Results
ResultsViewController <-- UITableView ordered by User ID
SavedResultsViewController <-- UITableView showing complete submission
The NSMutableArray gets passed through each ViewController.
My questions: What method of saving to file would best suit my needs? (.plist, filetype, etc). Viewing the results on Excel would be nice (but not essential). Where should the save take place? I was thinking when the last object is inserted into the array on Q4 ViewController, so it would be saved to file when the ENDViewController is popped, is this logical? Do I need to create a new Objective-C file to store the data? I have saw a few tutorials explaining this, declaring each object in a separate NSObject file, although I'm not sure if that is needed as my objects are already stored in the array.
As you've probably guessed I am quite new to Objective-C and iOS programming, so any help offered is greatly appreciated. I am not sure how else to describe my problem, so apologies if the question is still unclear.
Edit: I have learnt a bit more about Objective-C since creating this post, and have decided to save my data to a .csv file. This file is strictly for the purpose of emailing, it doesn't get displayed on the UITableView (I am currently implementing Core Data for that). This solution might help someone in the future, so here it is:
// Set column titles for .csv
NSArray *columnTitleArray = #[#"User ID", #"DoB", #"Question 1", #"Question 2", #"Question 3", #"Question 4"];
NSString *columnTitle = [columnTitleArray componentsJoinedByString:#","];
NSString *columnTitleToWrite = [columnTitle stringByAppendingString:#"\n"];
// Separate submissions from array into cells
NSString *questionResults = [self.submission componentsJoinedByString:#","];
NSString *questionResultsToWrite = [questionResults stringByAppendingString:#"\n"];
// Find documents directory
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
// Set file name and type
NSString *survey = [docPath stringByAppendingPathComponent:#"result.csv"];
// Create new file if none exists
if (![[NSFileManager defaultManager] fileExistsAtPath:survey]){
[[NSFileManager defaultManager] createFileAtPath:survey contents:nil attributes:nil];
// Set column titles for new file
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:survey];
[fileHandle writeData:[columnTitleToWrite dataUsingEncoding:NSUTF8StringEncoding]];
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:survey];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[questionResultsToWrite dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
You could save your array as a JSON file. This would allow you to view it in a text editor, but not a spreadsheet. The following code will write it out as JSON to a file:
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"yourdata.json"];
NSError *e = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:yourdata options:NSJSONWritingPrettyPrinted error:&e];
if (jsonData) {
[jsonData writeToFile:url.path atomically:YES];
}
You could also save your data as a plist file. The following will write the data to a plist:
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"yourdata.plist"];
NSError *e = nil;
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:yourdata format:NSPropertyListXMLFormat_v1_0 options:0 error:&e];
if (plistData) {
[plistData writeToFile:url.path atomically:YES];
}
Plists can be saved in binary or XML format, and the above will save it as XML format, which means the XML will also be readable with a text editor. Again, this won't be viewable as a spreadsheet. If you want to export your data in CSV format, you'll probably have to write your own code to output it.
You can use standard NSCoding protocol for saving data but in this case you can't read it in excel.
Or you can save it in xml.
Or you can manually create a csv (comma separated values) file and open it anywhere.
Implementation is up to you anyway :)

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.

how to add data string in .plist file?

I want put data string (for example loop for that create many url) in plist file on xcode.
this is my code (loop)
int count = 5;
NSString *a;
NSMutableArray *b = [[NSMutableArray alloc] initWithCapacity:count];
for (int i=1; i<= count; i++ ) {
a = [NSString stringWithFormat:#"http://192.168.1.114:81/book.php?page=%d",i];
[b addObject:a];
}
now I want save any page from top code in one row of .plist file but I dont know what can I do?
you can guidance me?
I'm not sure quite what you're shooting for, but if you're trying to extract the HTML from those URL strings, you could probably do something like:
// build path for filename
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *filename = [docsPath stringByAppendingPathComponent:#"test.plist"];
// create array of html results
NSMutableArray *htmlResults = [NSMutableArray array];
for (NSString *urlString in b)
{
// get the html for this URL
NSString *html = [NSString stringWithContentsOfURL:[NSURL URLWithString:urlString] encoding:NSUTF8StringEncoding error:nil];
// add the html to our array (or zero length string if it failed)
if (html)
[htmlResults addObject:html];
else
[htmlResults addObject:#""];
}
// save the html results to plist
[htmlResults writeToFile:filename atomically:YES];
A couple of thoughts:
Depending upon how many pages there are, I'm not sure if I'm crazy about loading all of the pages into a plist. I'd either
use some persistent storage like Core Data so I didn't have to hold all of the pages in memory, or
do some lazy loading of the HTML (load it as I need it))
Also, if I was going to load all of the pages, given that it could take a little time, I might have a progress view that I update with my progress, so the user wouldn't be looking at a frozen screen while the download was in progress.
If you just want to retrieve a single html file, then storing that in a plist might not make sense. I'd just write the html to a file (a HTML file, not a plist).
I wouldn't generally like to load the html in the main queue. I'd do a dispatch_async to perform this in a background queue. But I hesitate to go to far until you clarify precisely what you're looking for.
But hopefully this points you in the right direction, showing you how to retrieve data from the web pages.
If you wanted to save the individual html files to some local file (say X.html where X is the zero-based index number), you could do something like:
// identify the documents folder
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
// save the html results to local files
[b enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *html = [NSString stringWithContentsOfURL:[NSURL URLWithString:obj] encoding:NSUTF8StringEncoding error:nil];
if (html)
{
NSString *filename = [docsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%d.html", idx]];
[html writeToFile:filename atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
}];
Try [b writeToFile:#"myFile.plist" atomically:YES];, but make sure all the data in your array are representable in a plist.

Resources