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.2
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 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.2
import Cocoa// get URL to the the documents directory in the sandboxlet home = FileManager.default.homeDirectoryForCurrentUser// add a filenamelet fileUrl = home .appendingPathComponent("Documents") .appendingPathComponent("my_file") .appendingPathExtension("txt")// make sure the file existsguard 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 locationguard 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 bytesvar lineByteArrayPointer: UnsafeMutablePointer<CChar>? = nil// the smallest multiple of 16 that will fit the byte array for this linevar lineCap: Int = 0// initial iterationvar bytesRead = getline(&lineByteArrayPointer, &lineCap, filePointer)defer { // remember to close the file when done fclose(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)}
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)}