Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing CSV file in Swift

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.

like image 712
sunny k Avatar asked Aug 31 '15 14:08

sunny k


1 Answers

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)
            }
        }
    }
like image 150
theDuncs Avatar answered Oct 12 '22 23:10

theDuncs