Parsing CSV natively in Swift
August 2026
I recently needed to load a large CSV file into an app I was building. There are plenty of libraries on Github to parse CSV files with Swift, but for these small tasks my preferences go in this order:
- Use a built-in Apple framework
- Write it myself
- Import an external dependency
I didn’t recall there ever being a native Objective-C framework for CSV, but how hard could writing a parser myself be? It’s just splitting a text file by newlines and then by commas.
However once you start dealing with optionally quoted fields, header rows, multi-line fields and escaped characters—all of which my CSV contained—it just becomes a distracting side quest. Before I looked for something on Github I thought I’d quickly check the Swift docs. Turns out there is a native Swift library since iOS 15 called TabularData to load data for training ML models, with a DataFrame type that supports CSV parsing.
It appears to work in a similar way to the DataFrame available in Pandas, a popular library for data science in Python. Both allow operations like sorting, slicing and grouping the table without copying the original data, which is efficient on large datasets.
Using DataFrame
It’s easy to load a CSV file using the initialiser DataFrame.init(contentsOfCSVFile:). There are options for loading only a subset of rows or columns or customising the decoding but the defaults for those worked fine.
For this example CSV file:
"id","name","time"
1,"Alice","2007-07-09 08:59:00"
2,"Bob","2007-07-08 16:30:00"
3,"Carol","2007-07-08 11:37:00"
4,"Dan","2007-07-07 00:20:00"
Here’s how to print some basic details:
import TabularData
let url = URL(filePath: "data.csv")
var csv = try DataFrame(contentsOfCSVFile: url)
print(csv.shape) // (rows: 4, columns: 3)
print(csv.columns.map(\.name)) // ["id", "name", "time"]
csv.sort(on: "name", order: .descending)
print(csv.prefix(1))
/*
Prints:
┏━━━┳━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
┃ ┃ id ┃ name ┃ time ┃
┃ ┃ <Int> ┃ <String> ┃ <Date> ┃
┡━━━╇━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩
│ 0 │ 4 │ Dan │ 6/7/2007, 4:20 pm │
└───┴───────┴──────────┴───────────────────┘
1 row, 3 columns
*/
The types of each column are automatically detected and dates were parsed correctly, and there are loads of useful features for sorting and filtering data. However my actual data was not as clean as this example and I ran into a couple of issues.
Gotchas and bugs
While the API for DataFrame is generally simple enough to figure out, there are some functions which didn’t work as expected.
For example you can call grouped(by:) with a column name, but it doesn’t return anything that can be iterated over:
let groups = csv.grouped(by: "country")
for group in groups {
// ⛔️ No 'grouped' overloads produce result type that conforms to 'Sequence'
}
This version of the function returns a RowGroupingProtocol type, but to iterate you need a concrete RowGrouping which is created by providing the type of the column along with the name:
let groups = csv.grouped(by: ColumnID("country", String.self))
for group in groups {
print(group.key) // Andorra, Argentina, Australia, Austria, ...
}
Another problem I ran into was that column types are very strict. I had one column that usually contained dates, but occasionally would be empty. The date parsing worked fine but there is no way to make the column an optional Date?—it would just choke at the first empty value. Even when I specified CSVReadingOptions.dateParsers which return Date? I couldn’t get it to work.
Finally, if the header row has spaces around the names then it picks those up as part of the name. A header row containing "id", "name", "time" parses into column names of ["id", " name", " time"]. Note the leading spaces, even though they were outside the quotes. Then calling csv.sort(on: "name") would crash because that column name doesn’t exist as expected.
Maybe working around these bugs took longer than just writing a basic CSV parser myself, but I think it’s still worth it to use a first party framework when possible.
Any comments or questions about this post? ✉️ nick @ this domain.
— Nick Randall