Read a text file line by line in Swift?
Swift 3.0
if let path = Bundle.main.path(forResource: "TextFile", ofType: "txt") {
do {
let data = try String(contentsOfFile: path, encoding: .utf8)
let myStrings = data.components(separatedBy: .newlines)
TextView.text = myStrings.joined(separator: ", ")
} catch {
print(error)
}
}
The variable myStrings
should be each line of the data.
The code used is from: Reading file line by line in iOS SDK written in Obj-C and using NSString
Check edit history for previous versions of Swift.
Swift 5.5
The solution below shows how to read one line at a time. This is quite different from reading the entire contents into memory. Reading line-by-line scales well if you have a large file to read. Putting an entire file into memory does not scale well for large files.
The example below uses a while loop that quits when there are no more lines, but you can choose a different number of lines to read if you wish.
The code works as follows:
- create a URL that tells where the file is located
- make sure the file exists
- open the file for reading
- set up some initial variables for reading
- read each line using
getLine()
- close the file and free the buffer when done
You could make the code less verbose if you wish; I have included comments to explain what the variables' purposes are.
Swift 5.5
import Cocoa
// get URL to the the documents directory in the sandbox
let home = FileManager.default.homeDirectoryForCurrentUser
// add a filename
let fileUrl = home
.appendingPathComponent("Documents")
.appendingPathComponent("my_file")
.appendingPathExtension("txt")
// make sure the file exists
guard FileManager.default.fileExists(atPath: fileUrl.path) else {
preconditionFailure("file expected at \(fileUrl.absoluteString) is missing")
}
// open the file for reading
// note: user should be prompted the first time to allow reading from this location
guard let filePointer:UnsafeMutablePointer<FILE> = fopen(fileUrl.path,"r") else {
preconditionFailure("Could not open file at \(fileUrl.absoluteString)")
}
// a pointer to a null-terminated, UTF-8 encoded sequence of bytes
var lineByteArrayPointer: UnsafeMutablePointer<CChar>? = nil
// see the official Swift documentation for more information on the `defer` statement
// https://docs.swift.org/swift-book/ReferenceManual/Statements.html#grammar_defer-statement
defer {
// remember to close the file when done
fclose(filePointer)
// The buffer should be freed by even if getline() failed.
lineByteArrayPointer?.deallocate()
}
// the smallest multiple of 16 that will fit the byte array for this line
var lineCap: Int = 0
// initial iteration
var bytesRead = getline(&lineByteArrayPointer, &lineCap, filePointer)
while (bytesRead > 0) {
// note: this translates the sequence of bytes to a string using UTF-8 interpretation
let lineAsString = String.init(cString:lineByteArrayPointer!)
// do whatever you need to do with this single line of text
// for debugging, can print it
print(lineAsString)
// updates number of bytes read, for the next iteration
bytesRead = getline(&lineByteArrayPointer, &lineCap, filePointer)
}
If you have a huge file and don't want to load all data to memory with String
, Data
etc. you can use function readLine()
which reads content from standard input line by line until EOF is reached.
let path = "path/file.txt"
guard let file = freopen(path, "r", stdin) else {
return
}
defer {
fclose(file)
}
while let line = readLine() {
print(line)
}
This is not pretty, but I believe it works (on Swift 5). This uses the underlying POSIX getline
command for iteration and file reading.
typealias LineState = (
// pointer to a C string representing a line
linePtr:UnsafeMutablePointer<CChar>?,
linecap:Int,
filePtr:UnsafeMutablePointer<FILE>?
)
/// Returns a sequence which iterates through all lines of the the file at the URL.
///
/// - Parameter url: file URL of a file to read
/// - Returns: a Sequence which lazily iterates through lines of the file
///
/// - warning: the caller of this function **must** iterate through all lines of the file, since aborting iteration midway will leak memory and a file pointer
/// - precondition: the file must be UTF8-encoded (which includes, ASCII-encoded)
func lines(ofFile url:URL) -> UnfoldSequence<String,LineState>
{
let initialState:LineState = (linePtr:nil, linecap:0, filePtr:fopen(fileURL.path,"r"))
return sequence(state: initialState, next: { (state) -> String? in
if getline(&state.linePtr, &state.linecap, state.filePtr) > 0,
let theLine = state.linePtr {
return String.init(cString:theLine)
}
else {
if let actualLine = state.linePtr { free(actualLine) }
fclose(state.filePtr)
return nil
}
})
}
Here is how you might use it:
for line in lines(ofFile:myFileURL) {
print(line)
}