Can somebody please help me to find a way of reading a CSV file and parsing the contents into arrays of float, int and string values. There are many online code suggestions but they are all apparently broken in Xcode 9 (Swift 4). It appears that there have been significant changes recently and things that used to work no longer work. What I would really like to do is to be able to pick a text file from anywhere on my file system that has the appropriate pirvelages, convert that file to a string in Xcode, and then parse the string into arrays of different types (including string, int, float, double, etc.). When I try to run samples available on the internet I discover that many of the methods have changed or are no longer available.
I have code that works in Xcode 8.3. Despite trying many things, I cannot get Xcode 9 to do this. Xcode 9 can't even read the same file (same location) with identical code. The code is given below. Debugging, file = nil in Xcode 9.
let file: FileHandle? = FileHandle(forReadingAtPath: "/Volumes/Mac.Ext.J.1/Swift/CSV TRY 3/CSV.Parser/ECG1.csv")
if file != nil {
/
let data = file?.readDataToEndOfFile()
/
file?.closeFile()
/
let str = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print(str!)
}
else {
print("Ooops! Something went wrong!")
}
Is this possible in Xcode 9? If so, how? Does anybody have actual working code that does this in Xcode 9? It looks like I will be using Xcode 8.3 until this issue is resolved.
Thank you.
As with most things in programming, it pays to break down your problem into parts:
Reading a file
Breaking it into lines
Breaking the lines in columns
Converting a string to the appropriate data value
I’ll cover each in turn.
If you’re just getting started, a good place to start is a command line tool. This avoids the need for dealing with file selection dialogs, the App Sandbox, and so on. Here’s a tiny shell command line tool that’ll get you going:
import Foundation
func process(string: String) throws {
… your code here …
}
func processFile(at url: URL) throws {
let s = try String(contentsOf: url)
try process(string: s)
}
func main() {
guard CommandLine.arguments.count > 1 else {
print("usage: \(CommandLine.arguments[0]) file...")
return
}
for path in CommandLine.arguments[1...] {
do {
let u = URL(fileURLWithPath: path)
try processFile(at: u)
} catch {
print("error processing: \(path): \(error)")
}
}
}
main()
exit(EXIT_SUCCESS)
Note I’ve made some shortcuts here (like assuming the file is UTF-8 and printing errors to
stdout
not
stderr
) but it’s good enough to get your started.
Breaking a string into lines is super easy:
let lines = string.split(separator: "\n")
for line in lines {
… your code here …
}
Or, if you want to ‘see’ blank lines:
let lines = string.split(separator: "\n", omittingEmptySubsequences: false)
Note The above assumes platform-standard line breaks (LF).
Breaking lines into columns is easy for tab-separated files (literally take the previous code and change
\n
to
\t
) but it’s quite difficult for comma separate files. This is nothing to with Swift and everything to do with fundamental properties of CSV. To quote
The Fount of All Knowledge:
… in popular usage "CSV" is not a single, well-defined format.
For the moment let’s assume that column are never quoted, and thus cannot contain commas, in which case we can just split on commas:
let columns = line.split(separator: ",", omittingEmptySubsequences: false)
for column in columns {
… your code here …
}
In this case you really want to not omit empty sequences so that you can keep track of column numbers. Speaking of that, here’s how you’d do that:
for (columnNumber, column) in columns.enumerated() {
… your code here …
}
Note If the column can contain commas that you need to parse the line to work out the columns. How you do this depends on how your CSV file is set up.
Finally, converting a string (like
column
in the snippet above) to a number is done by an initialiser. For example, if you know the column is an integer you can do this:
if let i = Int(column) {
print("Int = \(i)")
} else {
… handle the fact that the column isn't the right type …
}
Or if you don’t know the types you can chain these together:
if let i = Int(column) {
print("Int = \(i)")
} else if let d = Double(column) {
print("Double = \(d)")
} else {
print("String = \(column)")
}
Note Again I’m taking shortcuts here. For example, this assumes the integer is rendered using standard ASCII digits, is in base 10, and so on.
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"