1

I have a cards array with text in the front and back. now I want to store this data in an csv file, which I created and added to my project. The following code creates the array of strings separated by a comma and then tries to write it to the file. But the file remains empty. There are no errors showing in the code. Any idea what I am doing wrong here?

let path = NSBundle.mainBundle().pathForResource("cardsFile", ofType:"csv")
    for i in 0...cards.count - 1{
        let card = [cards[i].front,cards[i].back]
        let cardRow:String = card.joinWithSeparator(",")
        print(cardRow)///confirmed - carRow is not empty
        do {
            try cardRow.writeToURL(NSURL(fileURLWithPath: path!), atomically: false, encoding: NSUTF8StringEncoding)
        }
        catch {}
    }
kangarooChris
  • 718
  • 1
  • 10
  • 21
  • Catch the exception and print it to see what is happening. – saagarjha Jun 06 '16 at 04:42
  • 1
    @ILikeTau - You're right, but technically, you're catching the error, not an "exception". – Rob Jun 06 '16 at 04:53
  • 1
    Is there some reason you need this as a CSV? I'd only use CSV if I needed to exchange this CSV with some other app. There are better formats if your intent is to save this array to be loaded back by the same app later (e.g. a `NSKeyedArchiver`). – Rob Jun 06 '16 at 05:12
  • http://stackoverflow.com/questions/37599632/swift-read-write-array-of-arrays-to-from-file/37602293#37602293 – Leo Dabus Jun 06 '16 at 05:13

1 Answers1

4

No errors are showing because you're catching the error and not doing anything. Add print(error) in the catch block:

do {
    try cardRow.writeToURL(NSURL(fileURLWithPath: path!), atomically: false, encoding: NSUTF8StringEncoding)
}
catch {
    print(error)
}

The issue is that you're trying to write to a file in the bundle, but the bundle is read-only. You should try writing to the Documents folder:

let fileURL = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
    .URLByAppendingPathComponent("cardsFile.csv")

By the way, writeToFile will replace the output file. If you want to write data to a file, appending data as you go along, you might want to use NSOutputStream:

let fileURL = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false).URLByAppendingPathComponent("cardsFile.csv")

guard let stream = NSOutputStream(URL: fileURL, append: false) else {
    print("unable to open file")
    return
}

stream.open()

for card in cards {
    let data = "\(card.front),\(card.back)\n".dataUsingEncoding(NSUTF8StringEncoding)!
    guard stream.write(UnsafePointer<UInt8>(data.bytes), maxLength: data.length) > 0 else {
        print("unable to write to file")
        break
    }
}

stream.close()

If the goal is to just save this locally, I wouldn't use a CSV format. I'd use a native format, such as a NSKeyedArchiver instead. So, first making Card conform to the NSCoding protocol by implementing init?(coder:) and encodeWithCoder():

class Card: NSObject, NSCoding {
    let front: String
    let back: String

    init(front: String, back: String) {
        self.front = front
        self.back = back
        super.init()
    }

    required convenience init?(coder aDecoder: NSCoder) {
        guard let front = aDecoder.decodeObjectForKey("front") as? String else { return nil }
        guard let back = aDecoder.decodeObjectForKey("back") as? String else { return nil }
        self.init(front: front, back: back)
    }

    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(front, forKey: "front")
        aCoder.encodeObject(back, forKey: "back")
    }

    override var description: String { get { return "<Card front=\(front); back=\(back)>" } }
}

Then, when you want to save:

let fileURL = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
    .URLByAppendingPathComponent("cardsFile.bplist")

if !NSKeyedArchiver.archiveRootObject(cards, toFile: fileURL.path!) {
    print("error saving archive")
}

And when you want to read it from the file:

guard let cards2 = NSKeyedUnarchiver.unarchiveObjectWithFile(fileURL.path!) as? [Card] else {
    print("problem reading archive")
    return
}

print(cards2)
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • thanks Rob, I got the NSOutputStream version to work. Took me a while to find the DocumentsDirectory and the file, but now I know that too. It's tough to be a beginner! – kangarooChris Jun 06 '16 at 06:21