How do you read data from a text file in Swift 3 (XCode 8)

Daniel No Kuroikaze picture Daniel No Kuroikaze · Jan 24, 2017 · Viewed 18.9k times · Source

first I would like to start with my current situation:

1) Current Situation:

I have a text file (data.rtf) I have also tried and am willing to use .plist or any other format to get a result.

I have been trying to read ANY data from this file, and show that data on a Label.

I have tried pre-populating the file, saving to the file before reading from it, and even just checking that the file exists, but every attempt has failed.

For 3 days I have searched various instructions and tutorials on how to do what is usually a fairly simple feature. I already did it using Objective-C, but I am new to Swift and unfortunately have to use it for this assessment and I am stuck.

2) Obstacles:

Every example I have looked at so far has been rejected by XCode as erroneous, ether for using terms that are no longer accepted like:

let path = (NSTemporaryDirectory() as NSString).stringByAppendingPathComponent("image.png")

Or lines that flat out do not work at all like:

let client = arrayClients[0]
    DisplayLabel.text = "\(client.ob("FirstName")!) \(client.objectForKey("LastName")!)"
    //DisplayLabel.text = client.  <- this part doesn't work

Every case either ends with code that is too out of date to work, or is too butchered after updating it to the replacements recommended to still work. The only cases where I have got the code to even build (removing syntax errors) has logical errors where the results either are nothing, or it crashes, most commonly with:

"NSCocoaErrorDomain Code = 260 "The file "data.rtf" couldn't be opened because there is no such file." "No such file or directory"

I think, even though the file exists in my XCode project, it is being searched for in the iPhone simulator, and for some reason it does not exist there. This is only a theory, and I have no idea how to fix that. I never encountered this problem when doing the same thing using Objective-C, where if I made a file and path to it, it worked, but for Swift it simply refuses to

3) The Code:

At this point, I am more interested in a code that works, than a fix to mine that does not, as mine has become so butchered in my attempts to solve this (as well as other XCode update related issues) that it is barely fit for purpose even if I get around this obstacle, but in case it helps, I will provide "some" of the different attempts I have pulled

// The "file not found code" :

let path = (NSTemporaryDirectory() as NSString).appendingPathComponent("data.rtf")

    do {
        // Get the contents
        let contents = try NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue)
        print(contents)
        DisplayLabel.text = contents as String?
    }
    catch let error as NSError {
        print("Ooops! let path = (NSTemp... did not work: \(error)")
    }

// Another variation with same result:

let file: FileHandle? = FileHandle(forReadingAtPath: "DataFile.plist")

    if file != nil {
        // Read all the data
        let data = file?.readDataToEndOfFile()

        // Close the file
        file?.closeFile()

        // Convert our data to string
        let str = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
        print(str!)
        DisplayLabel.text = str as String?
    }
    else {
        print("Ooops! Something went wrong!")
    }

// A version where the final line throws a (syntax -> Edit -> Fatal) error I have been unable to solve:

var dictClients = [String:String]()
    var arrayClients = NSMutableArray()


    let path = Bundle.main.path(forResource: "data", ofType: "rtf")

    let filemgr = FileManager.default

    if filemgr.fileExists(atPath: path!) {
        do {
            let fullText = try String(contentsOfFile: path!, encoding: String.Encoding.utf8)

            let readings = fullText.components(separatedBy: "\n") as [String]

            for i in 1..<readings.count {
                let clientData = readings[i].components(separatedBy: "\t")

                dictClients["FirstName"] = "\(clientData)"

                arrayClients.add(dictClients)
            }
        } catch {
            print("error: \(error)")
        }
    }

    let client = arrayClients[0]
    //DisplayLabel.text = client.
    //DisplayLabel.text = "\(client.ob("FirstName")!) \(client.objectForKey("LastName")!)"

// -Edit - I was able to fix the Syntax error with the below, but got a logic error instead

DisplayLabel.text = "\((client as AnyObject).object(forKey:"FirstName")!) \((client as AnyObject).object(forKey:"LastName")!)"

-- Edit New error --

"fatal error: unexpectedly found nil while unwrapping an Optional value"

I Hope this has somehow been clear, and I appreciate any help in resolving the problem. I have been working on this application since Thursday, and this particular issue since Sunday, with very little sleep or rest, even more computer is struggling to keep running under the strain and my focus is not as strong and I may not be as coherent as I think, so forgive any obvious mistakes I have made or poor communication I may be using.

Thanks in advance

Answer

vadian picture vadian · Jan 24, 2017

The main issue is that you cannot load rich text (RTF) formatted text into String. The Cocoa equivalent to RTF is NSAttributedString.

Load the RTF as Data, create an NSAttributedString and get the plain text with the string property.

var arrayClients = [[String:String]]() // do not use NSMutableArray in Swift
var dictClients = [String:String]()

if let url = Bundle.main.url(forResource:"data", withExtension: "rtf") {
    do {
        let data = try Data(contentsOf:url)
        let attibutedString = try NSAttributedString(data: data, documentAttributes: nil)
        let fullText = attibutedString.string
        let readings = fullText.components(separatedBy: CharacterSet.newlines)
        for line in readings { // do not use ugly C-style loops in Swift
            let clientData = line.components(separatedBy: "\t")
            dictClients["FirstName"] = "\(clientData)"
            arrayClients.append(dictClients)
        }
    } catch {
        print(error)
    }
}

However for that kind of data structure RTF is not appropriate. Better use JSON or property list.