I need to preload data into my tableView when the app launches. So i am using core data by parsing a .csv file. I am following this tutorial for this purpose. Here is my parseCSV function
func parseCSV (contentsOfURL: NSURL, encoding: NSStringEncoding, error: NSErrorPointer) -> [(stationName:String, stationType:String, stationLineType: String, stationLatitude: String, stationLongitude: String)]? {
// Load the CSV file and parse it
let delimiter = ","
var stations:[(stationName:String, stationType:String, stationLineType: String, stationLatitude: String, stationLongitude: String)]?
let content = String(contentsOfURL: contentsOfURL, encoding: encoding, error: error)
stations = []
let lines:[String] = content.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) as [String]
for line in lines {
var values:[String] = []
if line != "" {
// For a line with double quotes
// we use NSScanner to perform the parsing
if line.rangeOfString("\"") != nil {
var textToScan:String = line
var value:NSString?
var textScanner:NSScanner = NSScanner(string: textToScan)
while textScanner.string != "" {
if (textScanner.string as NSString).substringToIndex(1) == "\"" {
textScanner.scanLocation += 1
textScanner.scanUpToString("\"", intoString: &value)
textScanner.scanLocation += 1
} else {
textScanner.scanUpToString(delimiter, intoString: &value)
}
// Store the value into the values array
values.append(value as! String)
// Retrieve the unscanned remainder of the string
if textScanner.scanLocation < textScanner.string.characters.count {
textToScan = (textScanner.string as NSString).substringFromIndex(textScanner.scanLocation + 1)
} else {
textToScan = ""
}
textScanner = NSScanner(string: textToScan)
}
// For a line without double quotes, we can simply separate the string
// by using the delimiter (e.g. comma)
} else {
values = line.componentsSeparatedByString(delimiter)
}
// Put the values into the tuple and add it to the items array
let station = (stationName: values[0], stationType: values[1], stationLineType: values[2], stationLatitude: values[3], stationLongitude: values[4])
stations?.append(station)
}
}
return stations
}
this is my sample .csv file
Rithala,Underground,Yellow Line,28.7209,77.1070
But i am getting an error on this line
let station = (stationName: values[0], stationType: values[1], stationLineType: values[2], stationLatitude: values[3], stationLongitude: values[4])
stations?.append(station)
Fatal error : Array index out of range
What am i doing wrong ? Please help me.
Here's a foolproof way of parsing a CSV file into your swift code (I'm using Swift 5 here). I'll talk through each step for any beginners in the room.
Let's assume your CSV file looks like this:
Firstname,Last name,Age,Registered
Duncan,Campbell,40,True
Tobi,Dorner,36,False
Saskia,Boogarts,29,True
1). We need a struct (or Object) to hold each row of data. Let's use this:
struct Person {
var firstName: String
var lastName: String
var age: Int
var isRegistered: Bool
}
2) We also need a variable which holds an array of each Person
.
var people = [Person]()
3) Now - add the CSV file to your XCode project (you can drag and drop this into your project). Make sure it has a sensible name (e.g. data.csv).
4) You now need to "locate" the data that you want to use. Create a filepath which tells the code where to find your csv file:
guard let filepath = Bundle.main.path(forResource: "data", ofType: "csv") else {
return
}
5) Now we want to read the contents of this file. First let's convert the whole file into one long String.
var data = ""
do {
data = try String(contentsOfFile: filepath)
} catch {
print(error)
return
}
6) We now have a string with all out data in one line. We want to split this up into an array of strings, one string for each row in the data. (BTW, the \n
means "new line")
let rows = data.components(separatedBy: "\n")
7) We now have an array with 4 rows - one for the header titles, and 3 rows for each person in the data. We're not interested in the first header row, so we can remove that one. (Ignore this step if you don't have a header row in your data)
rows.removeFirst()
8) Now loop around each of rows. Each row is currently a string (e.g. Duncan,Campbell,40,True
) but we want to split it into an array of each of its 4 columns.
for row in rows {
let columns = row.components(separatedBy: ",")
9) We now have a array columns
which has 4 strings in it. Let's convert each column to the correct data type.
let firstName = columns[0]
let lastName = columns[1]
let age = Int(columns[2]) ?? 0
let isRegistered = columns[3] == "True"
10) And now we can create the Person object, and append it to our array.
let person = Person(firstName: firstName, lastName: lastName, age: age, isRegistered: isRegistered)
people.append(person)
Here's the full code for anyone who is interested:
struct Person {
var firstName: String
var lastName: String
var age: Int
var isRegistered: Bool
}
var people = [Person]()
func convertCSVIntoArray() {
//locate the file you want to use
guard let filepath = Bundle.main.path(forResource: "data", ofType: "csv") else {
return
}
//convert that file into one long string
var data = ""
do {
data = try String(contentsOfFile: filepath)
} catch {
print(error)
return
}
//now split that string into an array of "rows" of data. Each row is a string.
var rows = data.components(separatedBy: "\n")
//if you have a header row, remove it here
rows.removeFirst()
//now loop around each row, and split it into each of its columns
for row in rows {
let columns = row.components(separatedBy: ",")
//check that we have enough columns
if columns.count == 4 {
let firstName = columns[0]
let lastName = columns[1]
let age = Int(columns[2]) ?? 0
let isRegistered = columns[3] == "True"
let person = Person(firstName: firstName, lastName: lastName, age: age, isRegistered: isRegistered)
people.append(person)
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With