I'm implementing CHCSVParser into my iPhone app (thanks Dave!) however I'm really confused on how to use it. I've read the read-me and searched some questions on SO but still not 100% sure what to do.
I have a .CSV file with maybe 5000 rows of data and 3-4 columns.
I want this data to in return, load my UITableView along with its corresponding detailViewController.
So I'm assuming I need to somehow implement the API's array method but can anyone help get me started?
I'm glad you like it :)
Basically, CHCSVParser only parses CSV files. You give it a path to a CSV file, and it'll give you back a whole bunch of NSStrings. What you do beyond that point is entirely up to you.
So let's say you've included a CSV file in your iOS app called "Data.csv". Here's how you'd use CHCSVParser to parse it:
NSString *path = [[NSBundle mainBundle] pathForResource:#"Data" ofType:#"csv"];
NSError *error = nil;
NSArray *rows = [NSArray arrayWithContentsOfCSVFile:path encoding:NSUTF8StringEncoding error:&error];
if (rows == nil) {
//something went wrong; log the error and exit
NSLog(#"error parsing file: %#", error);
return;
}
At this point, rows is an array. Each element in rows is itself an array representing a single row in the CSV file. And each element of that array is an NSString.
So let's say your CSV file looks like this:
Barringer,Arizona,United States,Earth
"Chicxulub, Extinction Event Crater",,Mexico,Earth
Tycho,,,Moon
Lonar,Maharashtra,India,Earth
If you run it through the parser, you'll get back the equivalent of this:
[NSArray arrayWithObjects:
[NSArray arrayWithObjects:#"Barringer",#"Arizona",#"United States",#"Earth",nil],
[NSArray arrayWithObjects:#"Chicxulub, Extinction Event Crater",#"",#"Mexico",#"Earth",nil],
[NSArray arrayWithObjects:#"Tycho",#"",#"",#"Moon",nil],
[NSArray arrayWithObjects:#"Lonar",#"Maharashtra",#"India",#"Earth",nil],
nil];
What you do with it then is your business. The CSV parser doesn't know anything about UITableView, so you get to take this data and re-structure it in a way that you're comfortable dealing with and that fits in to your data model.
Also, remember that by using CHCSVParser, you agree to abide the terms under which it is made available. :)
Related
I am trying to parse a .csv file with approximately 30K lines. The following was mycode:
NSString *pathToGameLibrary = [[NSBundle mainBundle] pathForResource:#"GameLibrarySample" ofType:#"csv"];
NSArray *gameLibraryData = [NSArray arrayWithContentsOfCSVFile:pathToGameLibrary];
At the end of this execution, gameLibraryData is nil.
I can confirm that both the path and the csv file are valid because the following code spits out 30,000 lines of data to the console:
NSString *pathToGameLibrary = [[NSBundle mainBundle] pathForResource:#"GameLibrarySample" ofType:#"csv"];
NSString *fileString = [NSString stringWithContentsOfFile:pathToGameLibrary encoding:NSUTF8StringEncoding error:nil];
NSLog(#"%#", fileString);
Because this code does not work, I assume that the failure has to do with the length of the csv file. In an earlier question, CHCSV's author Dave DeLong suggests that:
"If this is a very big file, then you'll want to alloc/init a
CHCSVParser with the path to the local copy of the CSV file."
So...I've tried to follow what the original poster attempted with this code:
NSString *pathToGameLibrary = [[NSBundle mainBundle] pathForResource:#"GameLibrarySample" ofType:#"csv"];
CHCSVParser *file = [[CHCSVParser alloc] initWithContentsOfCSVFile:pathToGameLibrary];
[file setDelegate:self];
At which point the method callbacks which enable retrieval of data are:
- (void)parserDidBeginDocument:(CHCSVParser *)parser;
- (void)parserDidEndDocument:(CHCSVParser *)parser;
- (void)parser:(CHCSVParser *)parser didBeginLine:(NSUInteger)recordNumber;
- (void)parser:(CHCSVParser *)parser didEndLine:(NSUInteger)recordNumber;
- (void)parser:(CHCSVParser *)parser didReadField:(NSString *)field atIndex:(NSInteger)fieldIndex;
- (void)parser:(CHCSVParser *)parser didReadComment:(NSString *)comment;
- (void)parser:(CHCSVParser *)parser didFailWithError:(NSError *)error;
Thanks to yonosoytu for le assistance.
On Topic Post-Edit Question: So what counts as a "very big file" anyway? At which point is it prudent to use delegation over the simple 'arrayWithContentsofCSVFile'?
So what counts as a "very big file" anyway? At which point is it prudent to use delegation over the simple 'arrayWithContentsofCSVFile'?
The way that CHCSVParser reads and parses your CSV file is identical regardless of whether you're using the NSArray category method or directly interfacing with the delegate itself (you can see this for yourself here). So "a very big file" is really a CSV file which would be too large to hold in memory as an NSArray in its entirety. The memory consumption will depend both on the number of columns and the number of rows, so it's hard to give a definite limit.
What this means is that the problems you get from using the -arrayWithContentsOfCSV with large files shouldn't manifest themselves as returning nil, but as low memory warnings and the like. The fact you're getting nil returned suggests something else is going on.
Firstly, you say you've tried logging the file, so you know it exists - have you tried parsing a much smaller subset of that file? The problem might be that your CSV is badly formed. Or alternatively - and this is something I've had problems with myself when working with CSV on iOS - is your CSV encoded correctly? Sometimes if you're outputting directly from Excel you can end up with strange and weird text encodings that occasionally seem to break things.
You can always breakpoint and step-through the parsing methods in CHCSVParser for yourself and see what's going on. I hope this is of some use to you. As you've discovered, Dave DeLong actually posts here quite a bit - if you're lucky he'll probably come along and share more wisdom than I can.
I am implementing an audio player in my app. and I have a UITableView in which i show my list of audio tracks.
the list of audio is added into an array with
fileArray = [[NSMutableArray alloc] initWithObjects:
[[NSBundle mainBundle] pathForResource:#"A Million Years" ofType:#"mp3"],
[[NSBundle mainBundle] pathForResource:#"2 Million Years" ofType:#"mp3"],...
and in my cellForRowAtIndexPath I am doing
cell.textLabel.text = [[NSString stringWithFormat:#"%#", [self.fileArray objectAtIndex:indexPath.row]] stringByDeletingPathExtension];
but the problem is that it still shows me an entire extension "/Users/....". i only want the file name ("A Million Years", "2 Million Years") to be shown.
What am I doing wrong? I have been looking around for an answer but was never able to get it (perhaps due to wrong phrasing)
Thanks very much everybody. :)
You should use lastPathComponent if you want the last part of it. So:
cell.textLabel.text = [[[self.fileArray objectAtIndex:indexPath.row] lastPathComponent] stringByDeletingPathExtension];
That is not the extension. It's called the 'path'. (It would be good to at least get yourself to an acceptable level of knowledge in using a computing device before trying to program it...)
I suggest you use
[somePath lastPathComponent]
instead in order to get the filename only without the containing directories.
Also, the call to [NSString stringWithFormat:] is completely superfluous, wastes memory and processor time (which is unacceptable, especially on an embedded device).
I am working on a mobile app which needs a lot of data. Simply put, the data will be for multiple languages and consist of all the words possible for that language. The app would start with only the English language and a lot of it's words. Then the user can choose to download more languages and their data.
I am trying to figure out the best way to read/save/update this data. Should I create a plist file with English data to start with and just keep adding more data as user downloads new languages? Or should I save all the data in nsuserdefaults? Or, should I just include a text file with all the data and parse it on the fly?
Suggestions?
ps: i understand that as this is a mobile app, file space and parsing time have to be considered
Please read my answer here: https://stackoverflow.com/a/7215501/832065
As said in that answer, all NSUserDefaults are stored together in one plist.
A plist and NSUserDefaults are basically the same, however NSUserDefaults should ONLY be used for saving preferences and not a big amount of data. So don't use NSUserDefaults!
I would consider saving this in a plist (NSDictionary). Like this you can have all data sorted in that file. Simply set the "words" (NSString I assume) as object, and the language as key.
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
[dict setObject:words forKey:language];
[dict writeToFile:filePath atomically:YES];
same for reading:
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSString *words = [dict objectForKey:language];
EDIT:
If each word is assigned to a number (you can just use NSArray) it doesn't differ much:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
NSArray *words = [NSArray arrayWithObjects:allTheWords,nil];
[dict setObject:words forKey:language];
[dict writeToFile:filePath atomically:YES];
same for reading:
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSArray *words = [dict objectForKey:language];
NSString *word = [words objectAtIndex:numberAssignedToWord];
I hope this helps! :)
I would recommend using either an SQL database directly or Core Data. If you have a lot of data, you don't want to load that data completely into memory all the time. It is also easier to handle updates, changes or additions of data.
But your question is so general, and doesn't tell anything on what you actually want to do with the data, how large the data is in KByte or MByte, it is hard to give any good answer.
Writing a lot of data into a text file, user defaults, plist or something similar, doesn't seem the right choice. Using Core Data would be the default way to do such things on iOS.
I thing the easiest way would be to handle the data in a NSMutableArray and store it within the application using NSUserDefaults.
You can handle your data within a NSMutableArray, like so:
NSMutableArray *yourDataStuff = [[NSMutableArray alloc] init];
[yourDataStuff addObject:#"Your data 1"];
[yourDataStuff addObject:#"Your data 2"];
[yourDataStuff addObject:#"Your data 3, etc.."];
Then you can store it using NSUserDefaults, like so:
[[NSUserDefaults standardUserDefaults] setObject:yourDataStuff forKey:#"myData"];
And read your data, like so:
yourDataStuff = [[NSUserDefaults standardUserDefaults] objectForKey:#"myData"];
I think you cannot save large data in the NSUserDefault , and you maybe can not do IO with plist , i had done this , when you read or save , you must read in the whole .plist , and you may reveive memory warning , or crash. you can use C (fread,fwrite) write the data in a .txt , and you may read and write with data stream.
I am having some trouble populating a large plist into an array. Here is the snippet of code giving me problems:
// Populate the routes.
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"routes" ofType:#"plist"];
NSMutableArray *routes = [NSMutableArray arrayWithContentsOfFile:filePath];
NSLog(#"Routes: %#", routes);
// Populate the trips.
NSString *filePath2 = [[NSBundle mainBundle] pathForResource:#"trips" ofType:#"plist"];
NSMutableArray *trips = [NSMutableArray arrayWithContentsOfFile:filePath2];
NSLog(#"Trips: %#", trips);
My issue is that after displaying each mutable array in the logs, the log for the routes array displays just fine, but the log for the trips array simply doesn't appear at all. Usually when an issue occurs then the log will show something like "Trips: ( )", but that line doesn't appear at all in this case. The only difference I can see between the two instances is that the routes plist is an array with about 1000 dictionary objects and the trips plist has nearly 92,000 objects. Is there some sort of limit on the size of plists?
Thanks in advance.
"Is there some sort of limit on the size of plists?"
There isn't a limit to to the size of plists, but there is a limit to the amount of data that you can feed to an NSLog() command.
If trips were actually nil, the NSLog() call would succeed, and simply print out (null). The trips array is populated, however, which is why it's not printing out at all: NSLog() is saying, "sorry, there's no way I'm going to let you to print out all that".
I believe this has likely changed in more recent versions of OS X due to possible security concerns or performance issues. (In the past, users' hard drives would fill up with log files that were GB in size, caused by one process logging an error message hundreds of times a second; that is now limited to 500 logs per second). It's kind of confusing why nothing is printed out and you get no feedback from Xcode or anything, but I guess the system has no way of knowing whether your use of NSLog() is with good intentions or not.
According to the doc, if there is a parse problem or there is an issue with opening the file, nil will be returned. Is it possible in that massive file there is a bad character or a typo that might break the xml?
"
Return Value -->
An array containing the contents of the file specified by aPath. Returns nil if the file can’t be opened or if the contents of the file can’t be parsed into an array.
I'm creating a myDb.plist file in my resources folder and trying to read it, but it's not getting read. I'm using the following code.
NSString* plistPath = [[NSBundle mainBundle] pathForResource:#"myDb" ofType:#"plist"];
contentArray = [NSArray arrayWithContentsOfFile:plistPath];
contentArray is showing null.
What can I try to resolve this?
Make your plist data array and not dictionary. Dictionary also works but i tried with hit and trial. Also, print NSLog your data, if in case you need, to check the input from plist.