Press "Enter" to skip to content

Reading text files with arbitrary line endings in Swift 3

It’s easy enough in Swift to get an array of strings representing the lines of a text file:

  let contents = try! String(contentsOf: textFileURL)
  let lines = contents.components(separatedBy: .newlines)

…but what if the input might be a DOS format text file? CharacterSet.newlines contains both "\r" and "\n", so the code above will divide the text at each of them, and the array of strings will contain an extra empty string between every line.

Turns Out™ that Foundation has a number of APIs that understand all the ways line endings are represented in text files. The easiest way to read a text file with arbitrary line endings is to use the enumerateSubstrings(in:options:body:) method, specifying EnumerationOptions.byLines. This uses getLineStart(_:end:contentsEnd:for:) to recognize the line endings (see the documentation thereof for details) and is powerful enough to handle files with mixed line endings.

(In Objective-C these are the -enumerateSubstringsInRange:options:usingBlock: and -getLineStart:end:contentsEnd:forRange: methods of NSString.)

It’s straightforward to wrap this into an extension on String:


extension String {
func separatedIntoLines() -> [String] {
var lines: [String] = []
let wholeString = self.startIndex..<self.endIndex
self.enumerateSubstrings(in: wholeString, options: .byLines) {
(substring, range, enclosingRange, stopPointer) in
if let line = substring {
lines.append(line)
}
}
return lines
}
}

The name separatedIntoLines() seems like the best choice to describe the work being done; the alternative of a computed property simply called lines seems a little too terse to me. UPDATE 2018-02-13: After reviewing the Swift 4 API, perhaps splitIntoLines() would be a better name.