39

How do I load a text file line by line into an array with swift?

Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
Sebastian Krogull
  • 1,468
  • 1
  • 15
  • 31
  • 1
    See this post for an excellent discussion on the options for converting a text file to a line array. http://codereview.stackexchange.com/a/100813/79551 – Suragch Aug 15 '15 at 02:32

9 Answers9

62

Something along the lines of:

func arrayFromContentsOfFileWithName(fileName: String) -> [String]? {
    guard let path = NSBundle.mainBundle().pathForResource(fileName, ofType: "txt") else {
        return nil
    }

    do {
        let content = try String(contentsOfFile:path, encoding: NSUTF8StringEncoding)
        return content.componentsSeparatedByString("\n")
    } catch _ as NSError {
        return nil
    }
}

This approach assumes the file in question is located in your app bundle.

Cezar
  • 55,636
  • 19
  • 86
  • 87
10

With Swift 5, according to your needs, you can choose one of the 3 following ways in order to solve your problem.


#1. Using StringProtocol's components(separatedBy:) method

Foundation provides String a method called components(separatedBy:) with the following declaration:

func components(separatedBy separator: CharacterSet) -> [String]

Returns an array containing substrings from the string that have been divided by characters in the given set.

The code sample below shows how to use components(separatedBy:) with its parameter set to CharacterSet.newlines in order to load the content of a text file line by line into an array:

import Foundation

let path = Bundle.main.path(forResource: "Lorem Ipsum", ofType: "txt")!
let text = try! String(contentsOfFile: path, encoding: String.Encoding.utf8)
let lines = text.components(separatedBy: CharacterSet.newlines)

print(lines)

As an alternative, you can use the overloading of components(separatedBy:) that takes a parameter of type String. The code sample below shows how to use it:

import Foundation

let path = Bundle.main.path(forResource: "Lorem Ipsum", ofType: "txt")!
let text = try! String(contentsOfFile: path, encoding: String.Encoding.utf8)
let lines = text.components(separatedBy: "\n")

print(lines)

⚠️ You should however prefer the overloading of components(separatedBy:) that takes a CharacterSet parameter and use it with the value CharacterSet.newlines as this will manage all new line characters (U+000A ~ U+000D, U+0085, U+2028, and U+2029).


#2. Using StringProtocol's enumerateSubstrings(in:options:_:) method

Foundation provides String a method called enumerateSubstrings(in:options:_:). The code sample below shows how to use enumerateSubstrings(in:options:_:) with options parameter value set to String.EnumerationOptions.byLines in order to load the content of a text file line by line into an array:

import Foundation

let path = Bundle.main.path(forResource: "Lorem Ipsum", ofType: "txt")!
let text = try! String(contentsOfFile: path, encoding: String.Encoding.utf8)

let range = text.startIndex ..< text.endIndex
var lines = [String]()
text.enumerateSubstrings(in: range, options: String.EnumerationOptions.byLines) {
    (substring, range, enclosingRange, stop) in
    guard let substring = substring else { return }
    lines.append(substring)
}

print(lines)

#3. Using NLTokenizer's enumerateTokens(in:using:) method

NLTokenizer has a method called enumerateTokens(in:using:). enumerateTokens(in:using:) has the following declaration:

@nonobjc func enumerateTokens(in range: Range<String.Index>, using block: (Range<String.Index>, NLTokenizer.Attributes) -> Bool)

Enumerates over a given range of the string and calls the specified block for each token.

The code sample below shows how to use enumerateTokens(in:using:) in order to load the content of a text file line by line into an array:

import Foundation
import NaturalLanguage

let path = Bundle.main.path(forResource: "Lorem Ipsum", ofType: "txt")!
let text = try! String(contentsOfFile: path, encoding: String.Encoding.utf8)

let tokenizer = NLTokenizer(unit: .paragraph)
tokenizer.setLanguage(.english)
tokenizer.string = text

var lines = [String]()
tokenizer.enumerateTokens(in: text.startIndex ..< text.endIndex) { (range, attributes) -> Bool in
    let line = String(text[range])
    lines.append(line)
    return true
}
print(lines)
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
7

Swift 3 version based on the accepted answer:

func arrayFromContentsOfFileWithName(fileName: String) -> [String]? {
    guard let path = Bundle.main.path(forResource: fileName, ofType: "txt") else {
        return nil
    }

    do {
        let content = try String(contentsOfFile:path, encoding: String.Encoding.utf8)
        return content.components(separatedBy: "\n")
    } catch {
        return nil
    }
}
dbn
  • 458
  • 5
  • 12
6

If you are in Swift 2.0, you should use:

let path = NSBundle.mainBundle().pathForResource(fileName, ofType: nil)
if path == nil {
  return nil
}

var fileContents: String? = nil
do {
  fileContents = try String(contentsOfFile: path!, encoding: NSUTF8StringEncoding)
} catch _ as NSError {
  return nil
}
hippo_san
  • 360
  • 2
  • 12
5

This works only until Xcode 6.1 beta 1. In 6.1 beta 2 you must write this:

var err: NSError? = NSError()
let s = String(contentsOfFile: fullPath, encoding: NSUTF8StringEncoding, error: &err)

Where fullPath is a string containing the full path to the file and NSUTF8StringEncoding is a predefined constant for UTF8-Encoding.

You can also use NSMacOSRomanStringEncoding for Mac files or NSISOLatin1StringEncoding for Windows files.

s is an optional String and you can look if reading the file was successful:

if (s != nil)
{
    return (s!) // Return the string as "normal" string, not as optional string
}
Quill
  • 2,729
  • 1
  • 33
  • 44
j.s.com
  • 1,422
  • 14
  • 24
  • This also seems to have ceased to work as of the final release of Xcode 6. Try let string = String.stringWithContentsOfFile(fullPath, encoding: NSUTF8StringEncoding, error:&error) – Ash Oct 07 '14 at 14:45
2

My simple coding for you

 let path = NSBundle.mainBundle().pathForResource("FileName", ofType: "txt")
 var text = String(contentsOfFile: path!, encoding: NSUTF8StringEncoding, error: nil)!
 println(text)
 var array = text.componentsSeparatedByString("\n")
user3182143
  • 9,459
  • 3
  • 32
  • 39
1

Swift 3.0

if let path = Bundle.main.path(forResource: <#FileName#>, ofType: "txt")
{
    do
    {
        let str = try String(contentsOfFile:path, encoding: String.Encoding.utf8)
        return str.components(separatedBy: "\n")
    }
    catch
    {

    }
}
else
{
    return nil
}
Abhishek Jain
  • 4,557
  • 2
  • 32
  • 31
0

For me works as follow:

let myFileURL = NSBundle.mainBundle().URLForResource("listacomuni", withExtension: "txt")!
let myText = try! String(contentsOfURL: myFileURL, encoding: NSISOLatin1StringEncoding)
print(String(myText))
Alessandro Mattiuzzi
  • 2,309
  • 2
  • 18
  • 24
0

If you want to read a csv file of numeric data. (based on Cezar's answer)

func get_csv_data() -> [[Double]] {
    guard let path = NSBundle.mainBundle().pathForResource("filename_without_extension", ofType: "csv") else {
        return []
    }
    do {
        let content = try String(contentsOfFile:path, encoding: NSUTF8StringEncoding)
        let line_str_array = content.componentsSeparatedByString("\n")
        return line_str_array.map {
            let field_str_array = $0.componentsSeparatedByString(",")
            return field_str_array.map {
                Double($0)!
            }
        }
    } catch _ as NSError {
        return []
    }
}
Chris Gunawardena
  • 6,246
  • 1
  • 29
  • 45