Read a text file line by line in Swift?

IosArraysSwiftTextNsstring

Ios Problem Overview


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.

Ios Solutions


Solution 1 - Ios

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: https://stackoverflow.com/questions/11218318/reading-file-line-by-line-in-ios-sdk written in Obj-C and using NSString

Check edit history for previous versions of Swift.

Solution 2 - Ios

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

Solution 3 - Ios

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

Solution 4 - Ios

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

Solution 5 - Ios

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

Solution 6 - Ios

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"]

Solution 7 - Ios

In >= Swift 5.0 you do:

If the file is in the main Bundle:

if let bundleURL = Bundle.main.url(forResource: "YOUR_FILE_NAME", withExtension: "txt"),
   let contentsOfFile = try? String(contentsOfFile: bundleURL.path, encoding: .utf8) {
    let components = contentsOfFile.components(separatedBy: .newlines)
    print(components)
}

The components property will return an Array of Strings.

Solution 8 - Ios

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
    }

Solution 9 - Ios

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionScarletEnvyView Question on Stackoverflow
Solution 1 - IosCalebView Answer on Stackoverflow
Solution 2 - IosJason CrossView Answer on Stackoverflow
Solution 3 - IosalgalView Answer on Stackoverflow
Solution 4 - IosglaceView Answer on Stackoverflow
Solution 5 - IosNerdOfCodeView Answer on Stackoverflow
Solution 6 - IosBaseZenView Answer on Stackoverflow
Solution 7 - IosGabriel SóriaView Answer on Stackoverflow
Solution 8 - IosAlexander FedoseevView Answer on Stackoverflow
Solution 9 - IosiUriiView Answer on Stackoverflow