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
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.