Parsing CSV file in Swift

14,986

Solution 1

You are attempting to parse the file path rather than the contents of the file.

If you replace

let content = String(contentsOfURL: contentsOfURL, encoding: encoding, error: error)

with:

if let data = NSData(contentsOfURL: contentsOfURL) {
  if let content = NSString(data: data, encoding: NSUTF8StringEncoding) {
    //existing code
  }
}

then the code will work for your example file.

Solution 2

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)
            }
        }
    }
Share:
14,986
sunny k
Author by

sunny k

Updated on June 19, 2022

Comments

  • sunny k
    sunny k almost 2 years

    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.

  • sunny k
    sunny k over 8 years
    this is what i get when i print values [(file:///Users/sumeshagarwal/Library/Developer/CoreSimulato‌​r/Devices/ED48342B-E‌​512-41EB-88DD-50728C‌​8BC9AF/data/Containe‌​rs/Bundle/Applicatio‌​n/1BD48658-3F83-4B41‌​-BE83-731809E3C44B/M‌​etroCoreData.app/sta‌​tiondata.csv, 4, 0x00007fff5c189c50)]
  • sunny k
    sunny k over 8 years
    can u give me a link to a better tutorial to parse csv in swift ?
  • sunny k
    sunny k over 8 years
    thanx. perfect solution
  • pkc456
    pkc456 over 6 years
    Can you provide me parseCSV in Swift3. Need urgently
  • ngb
    ngb almost 3 years
    this doesn't handle commas between quotes e.g. : "$100,000"