Creating and writing multiple lines of text to a text file (CSV)? - swift

I'm trying to create a way for a user to sign in and store their username and password in a text file. This is for a school project, so it's not stored securely and I don't think I'll have time to set up/learn how to serialize the data anyways.
The main part I'm having issues with is trying to write the data as a CSV to later split the user's scores into an array based off of the data it received from the text file.
I've tried a different method of writing to the text file which was:
writeString.data(using: String.Encoding.utf8)?.write(to: fileURL, options: Data.WritingOptions.withoutOverwriting) But this didn't seem to work for me
struct UserAccount: Codable {
var username: String
var password: String
var scores: [Int]
}
var user = UserAccount(username: "", password: "", scores: [0])
func writeTextFile(_ user: UserAccount) {
//Creating text file to read and write user's data (username, password, and score values)
let fileName = "UserDataQuizApp"
let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
do {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: "UserDataQuizApp") {
try fileManager.removeItem(atPath: fileName)
}
} catch {print("error when deleting \(fileName)") }
// If the directory was found, we write a file to it and read it back
if let fileURL = dir?.appendingPathComponent(fileName).appendingPathExtension("txt") {
print("The path is: \(fileURL)")
do {
try user.username.write(to: fileURL, atomically: false, encoding: .utf8)
try ",".write(to: fileURL, atomically: false, encoding: .utf8)
try user.password.write(to: fileURL, atomically: false, encoding: .utf8)
for i in 0 ... user.scores.count {
try ",".write(to: fileURL, atomically: true, encoding: .utf8)
try String(user.scores[i]).write(to: fileURL, atomically: true, encoding: .utf8)
}
} catch {
print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription)
}
}
}
Writing the array to the text file doesn't work at all right now because it says the index is out of range, but when I commented it out and tried to only write the username and password, only the password would be in the file when I checked it. I was expecting the username, a comma, and then the password to be in it

Reason for crash is in line for i in 0 ... user.scores.count it should be either for i in 0 ..< user.scores.count or for score in user.scores.
And you are trying to write each text to a file.
That means only last write(to:.. will have effect. Everything written will be overwritten.
To fix that, create a single string contains all info then write it to the file.
For eg the write function should be like :
func writeTextFile(_ user: UserAccount) {
//Creating text file to read and write user's data (username, password, and score values)
let fileName = "UserDataQuizApp"
let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
do {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: "UserDataQuizApp") {
try fileManager.removeItem(atPath: fileName)
}
} catch {print("error when deleting \(fileName)") }
// If the directory was found, we write a file to it and read it back
if let fileURL = dir?.appendingPathComponent(fileName).appendingPathExtension("txt") {
print("The path is: \(fileURL)")
do {
var contents = [user.username, user.password]
contents.append(contentsOf: user.scores.map { "\($0)"})
let string = contents.joined(separator: ",")
print("contents \(string)")
try string.write(to: fileURL, atomically: false, encoding: .utf8)
} catch {
print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription)
}
}
}

Related

SwiftUI getting data from Data(contentsOf: file)

I'm trying to get some content from a .txt file by doing
let ruterQuery = loadR(filename: "content.txt")
func loadR( filename: String ) -> String {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
print(data)
return "done"
}
But when I print the data it only says "2045 bytes", how do I get the actual content of the file?
This is the content.txt, it's for an api url as a "query" of what fields to be returned. So the url will be "https://api.myapi.com/planner?query=" + content.txt
"{\n\tplaces(\n\t\tids:[\"ABC:StopPlace:8329\", \"ABC:StopPlace:0808\"]\n\t) {\n\t\tname\n\t\tid\n\t\testimatedCalls(timeRange: 72100, numberOfDepartures: 20) {\n\n\t\t\trealtime\n\t\t\trealtimeState\n\t\t\texpectedDepartureTime\n\t\t\tpredictionInaccurate\n\t\t\tdestinationDisplay {\n\t\t\t\tfrontText\n\t\t\t}\n\t\t\tquay {\n\t\t\t\tid\n\t\t\t}\n\n\t\t\tsituations {\n\t\t\t\tid\n\t\t\t\tlines {\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t\tsummary {\n\t\t\t\t\tvalue\n\t\t\t\t\tlanguage\n\t\t\t\t}\n\t\t\t\tstopPlaces {\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t\tdescription {\n\t\t\t\t\tvalue\n\t\t\t\t\tlanguage\n\t\t\t\t}\n\t\t\t\tvalidityPeriod {\n\t\t\t\t\tstartTime\n\t\t\t\t\tendTime\n\t\t\t\t}\n\t\t\t\tseverity\n\t\t\t\tsituationNumber\n\t\t\t}\n\n\t\t\tserviceJourney {\n\t\t\t\tid\n\t\t\t\tpublicCode\n\t\t\t\tprivateCode\n\t\t\t\tserviceAlteration\n\t\t\t\ttransportSubmode\n\t\t\t\tdirectionType\n\n\t\t\t\tnotices {\n\t\t\t\t\tid\n\t\t\t\t\ttext\n\t\t\t\t\tpublicCode\n\t\t\t\t}\n\n\t\t\t\tjourneyPattern {\n\t\t\t\t\tid\n\t\t\t\t\tline{\n\t\t\t\t\t\tid\n\t\t\t\t\t\tname\n\t\t\t\t\t\tpublicCode\n\t\t\t\t\t\ttransportMode\n\t\t\t\t\t\ttransportSubmode\n\t\t\t\t\t\tpresentation {\n\t\t\t\t\t\t\ttextColour\n\t\t\t\t\t\t\tcolour\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsituations {\n\t\t\t\t\t\t\tid\n\t\t\t\t\t\t\tdescription {\n\t\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\t\tlanguage\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tvalidityPeriod {\n\t\t\t\t\t\t\t\tstartTime\n\t\t\t\t\t\t\t\tendTime\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tsituations {\n\t\t\t\t\tid\n\t\t\t\t\tlines {\n\t\t\t\t\t\tid\n\t\t\t\t\t}\n\t\t\t\t\tsummary {\n\t\t\t\t\t\tvalue\n\t\t\t\t\t\tlanguage\n\t\t\t\t\t}\n\t\t\t\t\tstopPlaces {\n\t\t\t\t\t\tid\n\t\t\t\t\t}\n\t\t\t\t\tdescription {\n\t\t\t\t\t\tvalue\n\t\t\t\t\t\tlanguage\n\t\t\t\t\t}\n\t\t\t\t\tvalidityPeriod {\n\t\t\t\t\t\tstartTime\n\t\t\t\t\t\tendTime\n\t\t\t\t\t}\n\t\t\t\t\tseverity\n\t\t\t\t\tsituationNumber\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t}\n}\n"
The content of the file is JSON rather than simply text.
You get the the string representation with
func loadR(filename: String ) -> String {
let url = Bundle.main.url(forResource: filename, withExtension: "txt")!
let data = try! Data(contentsOf: url)
let string = String(data: data, encoding: .utf8)!
print(string)
return string
}
If the code crashes you made a design mistake. As files in the bundle are immutable at runtime the code must not crash.
Another bad practice are all the unnecessary whitespace characters. Unlike a human being the device doesn't care about prettyPrinting

Saving multiple lines of text in swift

I am attempting to store multiple lines of text in a local text file on an iphone. I have code which will create a text document, write data to that document and read data from this document.
However, if i try and add more text to this document, it will only store the most recent line of text which has been added.
The code I have for creating, writing and reading text from this document is as follows:
//Storing user rewards
let fileName = "Rewards"
let DocumentDirURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL = DocumentDirURL.appendingPathComponent(fileName).appendingPathExtension("txt")
//print("File Path: \(fileURL.path)")
let writeString = rewardLBL.text
do {
//writing to the file
try writeString?.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8)
} catch let error as NSError{
print("failed to write")
print(error)
}
var readString = ""
do {
readString = try String(contentsOf: fileURL)
}catch let error as NSError{
print("failed to readFile")
print(error)
}
print(readString)
I need this to allow for multiple entries of text to be stored, rather than just the most recent data which was written.
I suspect that due to the code being inside the 'viewDidLoadi()' method that it is constantly recreating the same document and thus always making a new version which overwrites the old Rewards.txt document.
Any help is greatly appreciated, thanks.
Since you are using write, it will overwrite whatever is written earlier.
try writeString?.write(to: fileURL, atomically: true, encoding:
String.Encoding.utf8)
You need to append line of text to your file, which will not overwrite previous written lines. Something like this:
writeString.data(using: String.Encoding.utf8)?.write(to: fileURL, options: Data.WritingOptions.withoutOverwriting)

Swift: unzipping file

I’m trying to get String from txt file inside the zip file using native libcompression library. Actually I use the code from
https://github.com/mw99/DataCompression/blob/master/Sources/DataCompression.swift.
At first, I was doing:
let zip = try? Data(contentsOf: "/.../test.zip")
let tmp: Data? = zip?.unzip()
let txt: String? = String(data: tmp!, encoding: .utf8)
But how do I get the contents of zip file and how do I get data from certain txt file?
ZIP Foundation supports accessing individual entries in ZIP archives.
You have to initialize an archive by passing a file URL to the Archive initializer.
Afterwards you can access a specific entry via subscripting:
let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("test.zip")
guard let archive = Archive(url: archiveURL, accessMode: .read) else {
return
}
guard let entry = archive["file.txt"] else {
return
}
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("out.txt")
do {
try archive.extract(entry, to: destinationURL)
} catch {
print("Extracting entry from archive failed with error:\(error)")
}
You can also directly access the contents of entry by using the closure based API. This allows you to process the entry without writing it to the file system first:
try archive.extract(entry, consumer: { (data) in
print(data.count)
})

Swift : Streaming / Writing out a CSV file

I am using my phone to record some sensor data and store it on device via SQLite via SharkORM(DBAccess).
I now want to write that data out to a CSV file however, I am now up to 1.6 million records.
Currently, I am looping through 1000 records, adding them to a string and at the end writing them out. However, there must be a better way to do it?
func writeRawFile()
{
let fileName = "raw"
let DocumentDirURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL = DocumentDirURL.appendingPathComponent(fileName).appendingPathExtension("csv")
var data = "time,lat,lon,speed,x_acc,y_acc,z_acc,gyro_x,gyro_y,gyro_z,compass,orientation\r\n"
let count = RawReading.query().count()
var counter = 0;
let df = DateFormatter()
df.dateFormat = "y-MM-dd H:m:ss.SSSS"
for i in stride(from:0, to: count, by: 1000)
{
autoreleasepool {
for result in RawReading.query().offset(Int32(i)).limit(1000).fetch()
{
if let raw : RawReading = result as? RawReading
{
if (Double(raw.speed!) > 3.0) //1 Meter per Second = 2.236936 Miles per Hour
{
//print(df.string(from: raw.date!))
data += "\(df.string(from: raw.date!)),\(raw.lat!),\(raw.lon!),\(raw.speed!),\(raw.x!),\(raw.y!),\(raw.z!),\(raw.xx!),\(raw.yy!),\(raw.zz!),\(raw.compass!),\(raw.orientation!)" + "\r\n"
counter += 1
}
}
}
print ("Written \(i) of \(count)")
}
}
print("Count \(count) \(counter)")
//write the file, return true if it works, false otherwise.
do{
try data.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8 )
} catch{
print("error")
}
}
Open a FileHandle for writing, then build and write each line separately, so that you don't have to keep the entire file contents
in memory:
do {
let file = try FileHandle(forWritingTo: fileURL)
defer { file.closeFile() }
for <... your loop ...> {
let line = ... // build one CSV line
file.write(line.data(using: .utf8)!)
}
} catch let error {
// ...
}
You can also write to a temporary file first and then rename it to the
actual file, in order to avoid a damaged file if anything
went wrong.

Writing to log file seems to overwrite previous save

As an exercise, I'm trying to write a logging class that logs strings to a text file. I've got my application to write and read from the file. However, if I try to log multiple times it seems to only pick up the most recent log.
Attempt
writing
private let file = "logfile.txt"
func write(text: String) {
let path = getDocumentsDirectory()
do {
try text.writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding)
}
catch let error {
print("error: \n \(error)")
}
}
reading
func read() {
let path = getDocumentsDirectory()
do {
let text2 = try String(contentsOfFile: path, encoding: NSUTF8StringEncoding)
print("From Log: \n \(text2)")
}
catch let error {
print("error: \n \(error)")
}
}
func getDocumentsDirectory() -> String {
guard let dir : NSString = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first else {
return ""
}
let documentsDirectory : String = dir.stringByAppendingPathComponent(file)
return documentsDirectory
}
Result
When I try to read my file I only get the last line saved.
Question
If my goal is to endlessly append new logs to a file, and to read the log file in bulk. What changes to my code do I need to make?
more details:
I'm writing to the file on application load:
Logger.shared.write("instance one writes")
Logger.shared.write("instance one writes again")
Logger.shared.write("instance one writes yet again")
and then attempting to read:
Logger.shared.read()
output:
From Log:
instance one writes yet again
The writeToFile(_:atomically:encoding:) method provided by Foundation replaces the contents of the given file. There are several ways of appending to files:
Plain ol’ fopen (with mode "a") and fwrite.
NSOutputStream, such as NSOutputStream(toFileAtPath: mypath, append: true), using stream.write(bytes, len) to write data.
Perhaps the easiest, NSFileHandle, such as NSFileHandle(forWritingAtPath: mypath), using seekToEndOfFile() and writeData().