How to read and write to CSV files using Xuni FlexGrid for iOS
Several weeks ago, we explored reading and writing CSV files using Xuni for Android. This process has some general similarities on iOS and a few differences to working with CSV files on Android. In this article, we’ll examine loading data from and saving data to a local CSV file on the iOS platform using Xuni FlexGrid. This guide uses the same stock data CSV that we used for our Android example. We’ll deal with loading the data from a CSV saved in the mainBundle, optionally loading from the documents directory, and saving the edited FlexGrid to the documents directory.
Reading a CSV File
The first step to implementing our example is to create a class that handles reading a CSV file and parsing the data (much like we did on Android). The implementation is a little different though as our file contents will be read in as a long string. We'll still need to split the data into individual lines, and also split the values separated by a comma. We'll then iterate through the data and add it directly to our model (which we'll cover in the next section):
@implementation ReadCSV
+(NSMutableArray *) read: (NSString *)fileContents{
NSMutableArray *array = [[NSMutableArray alloc] init];
NSArray *lines = [fileContents componentsSeparatedByString:@"\\r\\n"];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"dd-MMM-yyyy"];
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
for (int i = 0; i < lines.count -1; i++) {
StockData *finance = [[StockData alloc] init];
NSArray *strings = [lines[ i ] componentsSeparatedByString:@","];
finance.ticker = (NSString *)strings[0];
finance.date = [dateFormatter dateFromString:strings[1]];
finance.open = [numberFormatter numberFromString:strings[2]];
finance.high = [numberFormatter numberFromString:strings[3]];
finance.low = [numberFormatter numberFromString:strings[4]];
finance.close = [numberFormatter numberFromString:strings[5]];
finance.volume = [numberFormatter numberFromString:strings[6]];
[array addObject:finance];
}
return array;
}
@end
Creating a Model for your Data
The model allows us to strongly type our data, and provides some form to what would otherwise be a large group of strings that we've read from our CSV. Since we're working on iOS we'll be using many descendants of NSObject. A single line of the stock.csv file looks like this: GOOG,01-Sep-2015,602.359985,612.859985,594.099976,597.789978,3680200 These values reflect a ticker, date, open, high, low, close, and volume, which means we’ll need to create properties that correspond to each. Likewise, we’ll provide a mechanism for taking the the FlexGrid's itemsSource (which is an NSMutableArray on iOS) after it’s been edited and converting it back into a string for the purpose of saving the data to disk. Our header file declares the the properties we'll need:
@interface StockData : NSObject
@property NSString *ticker;
@property NSNumber \*open, \*high, \*low, \*close, *volume;;
@property NSDate *date;
+(NSString *)convertToString: (NSMutableArray *) array;
@end
The implementation file has our convertToString method which we'll use later when we need to save the file:
@implementation StockData
+(NSString *)convertToString:(NSMutableArray *) array{
NSString *csv = [[NSString alloc] init];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"dd-MMM-yyyy"];
for (int i = 0; i < array.count -1; i++) {
StockData \*finance = (StockData \*)[array objectAtIndex:i];
csv = [csv stringByAppendingString:finance.ticker];
csv = [csv stringByAppendingString:@","];
csv = [csv stringByAppendingString:[dateFormatter stringFromDate:finance.date]];
csv = [csv stringByAppendingString:@","];
csv = [csv stringByAppendingString:[finance.open stringValue]];
csv = [csv stringByAppendingString:@","];
csv = [csv stringByAppendingString:[finance.high stringValue]];
csv = [csv stringByAppendingString:@","];
csv = [csv stringByAppendingString:[finance.low stringValue]];
csv = [csv stringByAppendingString:@","];
csv = [csv stringByAppendingString:[finance.close stringValue]];
csv = [csv stringByAppendingString:@","];
csv = [csv stringByAppendingString:[finance.volume stringValue]];
csv = [csv stringByAppendingString:@"\\r\\n"];
}
return csv;
}
@end
Saving to a CSV File
Saving the data from our grid back to CSV is essentially a reversal of the earlier process of reading the CSV. First, we'll need to provide a location to write the file. For this example, we’re going to save the file to the documents directory. Unlike Android, there's no notion of external storage since iOS devices are much more uniform in design and available space, and we also don't have to concern ourselves with permissions in the manifest. The documents directory isn't something that a user will access directly on their device, though it is something that we can access on our simulator via the Finder on a Mac or in code in our application. The file path should be something along the lines of: Users\"UserName"\Library\Developer\CoreSimulator\Devices\ "DeviceID"\data\Containers\Data\Application\"ApplicationID"\Documents The implementation file is fairly straightforward, and it requires using the correct paths to write a string to the documents directory. If some type of error is encountered we'll return it so that we can notify the user elsewhere.
@implementation SaveCSV
+(NSError *)save:(NSMutableArray *) array{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
NSString *filename = [docDir stringByAppendingPathComponent:@"stock.csv"];
NSError *error = NULL;
BOOL written = [[StockData convertToString: array] writeToFile:filename atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (!written)
return error;
return nil;
}
@end
Setting up the ViewController
The final step is to configure the ViewController so that it loads data into our FlexGrid when the application starts. We'll also provide a button that allows us to save the modified grid back to CSV. I’m including a “built-in” stock.csv file which is included as part of the mainBundle and is present in our XCode project: Much like on Android, this application will check if a modified stock.csv is present in the Documents folder. If it exists then that data will be loaded into the FlexGrid. If the file hasn't been created yet we will instead load the stock.csv included with the application in the mainBundle. This logic will all be added into our viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.edgesForExtendedLayout = UIRectEdgeNone;
self.title = @"Xuni Stock CSV";
grid = [[FlexGrid alloc] init];
saveButton = [[UIButton alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pathDocs = [documentsDirectory stringByAppendingPathComponent:@"stock.csv"];
NSString *fileContents;
if ([[NSFileManager defaultManager] fileExistsAtPath:pathDocs])
{
fileContents = [[NSString alloc] initWithContentsOfFile:pathDocs encoding:NSUTF8StringEncoding error:NULL];
}
else{
NSString *pathBundle = [[NSBundle mainBundle] pathForResource:@"stock" ofType:@"csv"];
fileContents = [NSString stringWithContentsOfFile:pathBundle encoding:NSUTF8StringEncoding error:nil];
}
grid.itemsSource = [ReadCSV read:fileContents];
saveButton = [UIButton buttonWithType:UIButtonTypeSystem];
[saveButton setTitle:@"Save CSV" forState:UIControlStateNormal];
[saveButton addTarget:self action:@selector(saveButtonClicked) forControlEvents:UIControlEventTouchUpInside];
[saveButton.titleLabel setTextAlignment:NSTextAlignmentCenter];
[self.view addSubview:grid];
[self.view addSubview:saveButton];
}
We'll also add some code to handle the saveButton being Clicked. Here, we'll call the save method of SaveCSV which we wrote earlier and provide an alert if it returns an error:
- (void)saveButtonClicked{
NSError *error = [SaveCSV save:grid.itemsSource];
NSString *message;
NSString *title;
if (!error) {
title = @"Succes";
message = @"CSV saved succesfully";
}
else {
title = @"Failure";
message = [error description];
}
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
}
Wrapping Things Up
Xuni FlexGrid can easily provide a similar experience across multiple platforms which a little help from the API's available on iOS and Android (or alternatively via Xamarin). With a small amount of effort, you can provide a very clean and easy mechanism for viewing and editing your data on either platform.