76

I just started learning Swift. I have got my code to read from the text file, and the App displays the content of the entire text file. How can I display line by line and call upon that line multiple times?

TextFile.txt contains the following:

1. Banana 2. Apple 3. pear 4. strawberry 5. blueberry 6. blackcurrant 

The following is what currently have..

 if let path = NSBundle.mainBundle().pathForResource("TextFile", ofType: "txt"){ var data = String(contentsOfFile:path, encoding: NSUTF8StringEncoding, error: nil) if let content = (data){ TextView.text = content } 

If there is another way of doing this please let me know. It would be much appreciated.

2
  • OK, there seems to be only one line in this file. Do you mean separate words instead of lines? Commented Aug 3, 2015 at 3:32
  • Sorry, its was suppose to be a list using a /n to separate it Commented Aug 3, 2015 at 21:53

11 Answers 11

102

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.

Sign up to request clarification or add additional context in comments.

6 Comments

Hey also, if I was to display two lines at a time, would that be = myStrings[5,1] ...?
I don't understand what the if let content = (data){ line is for. What do the parenthesis do here?
@Flimm The parenthesis do not serve any purpose here. I left the original code from the question and only modified the content inside the if let.
This does not read a text file line-by-line. It reads the entire file, saves it all into data, and then returns it all as an array of lines.
Following @algal comment, this answer does not work for very big files. For people who need to read chunks of the a big file little by little, check this answer: stackoverflow.com/questions/24581517/…
|
36

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:

  1. create a URL that tells where the file is located
  2. make sure the file exists
  3. open the file for reading
  4. set up some initial variables for reading
  5. read each line using getLine()
  6. 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) } 

1 Comment

Good answer! Though the question does not assume it is on macOS platform, it still has lots to be learnt from this answer.
22

In iOS 15.0+ and macOS 12.0+, with Swift concurrency and FileHandle, you can use lines from bytes:

func readLineByLine(from fileUrl: URL) async throws { let handle = try FileHandle(forReadingFrom: fileUrl) for try await line in handle.bytes.lines { // do something with `line` } try handle.close() } 

Or, alternatively, just use lines from URL:

func readLineByLine(from fileUrl: URL) async throws { for try await line in fileUrl.lines { // do something with `line` } } 

Both of these avoid loading the whole asset into memory at one time and are therefore memory efficient.

2 Comments

Almost missed your response in the jungle of suggestions. This is by far the best answer today, thank you!
Yes, the lines property seems to do it. Documentation says: "Use this property with Swift’s for-await-in syntax, to read the contents of a URL line-by-line"
17

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) } 

3 Comments

I don't understand why this answer doesn't have more upvotes. It's by far the most correct answer as it actually reads the file line by line in a very simple way.
rare crash on fclose(file)
This is the worst option how you can read file content, because it firstly push file content to stdin and then it reads from it. How you can be shure that any other thread is not pushing anything else to stdin?
6

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) } 

2 Comments

The requirement of having to go through ALL lines is a bit... hard? For text documents of tens or hundreds of MB that might not always be what one wants to do. Can this be improved somehow?
You're right that's a deficiency of this solution.The most obvious improvement, I think, would be not to return an UnfoldSequence, but a custom type that implemented Sequence, and which was smart enough that if you didn't finish iterating the sequence but instead destroyed the instance, then it would still do cleanup in its deinit method.
5

Probably the simplest, and easiest way to do this in Swift 5.0, would be the following:

import Foundation // Determine the file name let filename = "main.swift" // Read the contents of the specified file let contents = try! String(contentsOfFile: filename) // Split the file into separate lines let lines = contents.split(separator:"\n") // Iterate over each line and print the line for line in lines { print("\(line)") } 

Note: This reads the entire file into memory, and then just iterates over the file in memory to produce lines....

Credit goes to: https://wiki.codermerlin.com/mediawiki/index.php/Code_Snippet:_Print_a_File_Line-by-Line

1 Comment

This does not read a text file line-by-line. It reads the entire file, saves it all into a string, splits that into an array of strings, and then prints them one by one. If your string is too large to fit in memory, this will fail.
2

Update for Swift 2.0 / Xcode 7.2

 do { if let path = NSBundle.mainBundle().pathForResource("TextFile", ofType: "txt"){ let data = try String(contentsOfFile:path, encoding: NSUTF8StringEncoding) let myStrings = data.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) print(myStrings) } } catch let err as NSError { //do sth with Error print(err) } 

Also worth to mention is that this code reads a file which is in the project folder (since pathForResource is used), and not in e.g. the documents folder of the device

Comments

0

You probably do want to read the entire file in at once. I bet it's very small.

But then you want to split the resulting string into an array, and then distribute the array's contents among various UI elements, such as table cells.

A simple example:

 var x: String = "abc\ndef" var y = x.componentsSeparatedByString("\n") // y is now a [String]: ["abc", "def"] 

2 Comments

Yes! :D thats exactly what I'm asking. so would x be assigned to = data?
Swift 4: var y = x.components(separatedBy: "\n")
0

One more getline solution:

  • Easy to use. Just copy past.
  • Tested on real project.
extension URL { func foreachRow(_ mode:String, _ rowParcer:((String, Int)->Bool) ) { //Here we should use path not the absoluteString (wich contains file://) let path = self.path guard let cfilePath = (path as NSString).utf8String, let m = (mode as NSString).utf8String else {return} //Open file with specific mode (just use "r") guard let file = fopen(cfilePath, m) else { print("fopen can't open file: \"\(path)\", mode: \"\(mode)\"") return } //Row capacity for getline() var cap = 0 var row_index = 0 //Row container for getline() var cline:UnsafeMutablePointer<CChar>? = nil //Free memory and close file at the end defer{free(cline); fclose(file)} while getline(&cline, &cap, file) > 0 { if let crow = cline, // the output line may contain '\n' that's why we filtered it let s = String(utf8String: crow)?.filter({($0.asciiValue ?? 0) >= 32}) { if rowParcer(s, row_index) { break } } row_index += 1 } } } 

Usage:

 let token = "mtllib " var mtlRow = "" largeObjFileURL.foreachRow("r"){ (row, i) -> Bool in if row.hasPrefix(token) { mtlRow = row return true // end of file reading } return false // continue file reading } 

Comments

0

Here is an example of writeing and reading a text file one line at a time in Swift version 5. Reads one line in at a time and includes EOF detection

// // main.swift // IO // // Created by Michael LeVine on 8/30/22. // import Foundation let file = "file.txt" //this is the file. we will write to and read from it let text = "some text\n" //just a text // test file will be placed on deasktop let dir = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first let fileURL = dir!.appendingPathComponent(file).path let fileURL2 = dir!.appendingPathComponent(file) let fileManager = FileManager.default // the following variable used by eof detection which also use var fileManager internally var eofOffset: UInt64 = 0 if fileManager.fileExists(atPath: fileURL) { do { try fileManager.removeItem(atPath: fileURL)} catch { print("Error removeing old \(fileURL)") exit(1) } } // create the new file fileManager.createFile(atPath: fileURL, contents:Data(" ".utf8), attributes: nil) var fileHandle = FileHandle(forWritingAtPath: fileURL) //writing for _ in 1...10 { fileHandle!.write(text.data(using: .utf8)!) } do { try fileHandle!.close() } catch { print("write close error \(error)") exit(1) } // now to read text file by 2 methods // first use String to read whole file in one gulp let contents = try! String(contentsOfFile: fileURL) let lines = contents.split(separator: "\n") var i: Int = 0 // print out one way for line in lines { print("\(i) \(line)") i=i+1 } // printout another way for j in 0...9 { print("\(i) \(j) \(lines[j])") i = i + 1 } //Open up to see about reading line at a time fileHandle = FileHandle(forReadingAtPath: fileURL) eofInit() // must be called immediately after fileHandle init var outputLine: String = "" i = 0 // read a line and print it out as recieved while true { outputLine = getLine() if eofTest(){ if outputLine.count > 0 { print("\(i) \(outputLine)") } exit(1) } print("\(i) \(outputLine)") i = i + 1 } // function reads one character at each call and returns it as a 1 character string // is called only by "getLine" func getChar() -> String { var ch: Data if eofTest() { return "" } do { try ch = fileHandle!.read(upToCount: 1)! // read 1 character from text file } catch { print("read 1 char \(error)") exit(1) } let ch2: UnicodeScalar = UnicodeScalar(ch[0]) // convert to unicode scaler as intermediate value let ch3: String = String(ch2) // Now create string containing that one returned character return ch3 // and pass to calling function } // read in whole line one character at a time -- assumes line terminated by linefeed func getLine() -> String { var outputLine : String = "" var char : String = "" // keep fetching characters till line feed/eof found lineLoop: while true { // its an infinite loop if eofTest() { break lineLoop } char = getChar() // get next character if char == "\n" { // test for linefeed break lineLoop // if found exit loop } outputLine.append(char) // lf not found -- append char to output line } return outputLine // got line -- return it to calling routine } //eof handleing //init routine must be called immediately after fileHandle inited to get current position // at start of file func eofInit() { var beginningOffset: UInt64 = 0 do { try beginningOffset = fileHandle!.offset() try eofOffset = fileHandle!.seekToEnd() try fileHandle!.seek(toOffset: beginningOffset) } catch { print("Init eof detection error \(error)") } } func eofTest() -> Bool{ var current: UInt64 = 0 do { current = try fileHandle!.offset() } catch { print("eof test get current \(error)") exit(1) } if current < eofOffset { return false } else { return true } } 

Comments

0

Based on Jason Cross answer simplified version line by line reader(gist).

import Darwin class FileLineReader { init?(path: String, removeNewLineOnEnd: Bool = true) { file = fopen(path, "r") self.removeNewLineOnEnd = removeNewLineOnEnd if file == nil { return nil } } deinit { fclose(file) } var iterator: AnyIterator<String> { return AnyIterator(self.getNextLine) } func getNextLine() -> String? { var line: UnsafeMutablePointer<CChar>! var linecap: Int = 0 defer { free(line) } if getline(&line, &linecap, file) > 0 { if removeNewLineOnEnd { var i = 0 while line[i] != 0 { i += 1 } if i > 0 && line[i-1] == 10 { // new line symbol line[i-1] = 0 } } return String(cString: line) } else { return nil } } private let file: UnsafeMutablePointer<FILE>! private let removeNewLineOnEnd: Bool } 

iUrii approach may not work if you need to open several files.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.