Convert String to Array Items - ios

The below code create the file and puts the data in it but it saves as the following:
["123", "456", "", "789"] How do I get a new item for each paragraph? the idea is the song lyrics would save to the file based on where the verses start and end. I am new to swift so there may be a better way to do this
#IBOutlet weak var songName: UITextField!
#IBOutlet weak var newLyrics: UITextView!
#IBAction func saveSong(_ sender: Any) {
let sName = songName.text!
let fileManager = FileManager.default
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let path = documentDirectory.appending("/Lyrics/\(sName).plist")
let lyrics = newLyrics.text.components(separatedBy: "\n\n")
print(lyrics)
if let tDocumentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
let filePath = tDocumentDirectory.appendingPathComponent("Lyrics")
if !fileManager.fileExists(atPath: filePath.path) {
do {
try fileManager.createDirectory(atPath: filePath.path, withIntermediateDirectories: true, attributes: nil)
} catch {
NSLog("Couldn't create document directory")
}
}
NSLog("Document directory is \(filePath)")
}
if(!fileManager.fileExists(atPath: path)){
print(path)
var lyricArray = [String]()
lyricArray.append("\(lyrics)")
// any other key values
let lyricData = NSArray(array: lyricArray)
let answer = lyricData.write(toFile: path, atomically: true)
print("File Created? \(answer)")
} else {
print("File Exists")
}
I expect the output to be
Item0 "123"
Item1 "456"
actual output is
Item0 ["123", "456", "", "789"]

You are appending the description of an array to lyricArray with string interpolation (\(...)).
The description of an array is one literal string with format "[\"item1\", \"item2\"]".
You have to append the contents of the array
Replace
lyricArray.append("\(lyrics)")
with
lyricArray.append(contentsOf: lyrics)
or drop lyricArray completely and simply write
let lyricData = NSArray(array: lyrics)
or – highly recommended –
do {
let lyricData = try PropertyListSerialization.data(fromPropertyList: lyrics, format: .xml, options: 0)
} catch { print(error) }
Notes:
Don't use NSArray to handle property lists in Swift. Use PropertyListSerialization.
Never print meaningless literal strings in a catch clause like Couldn't do that. Print the error.
And why do get the path to the documents folder twice with different API (okay, first the path and then the URL)?

Related

How to create a CSV file using Swift

In an app I created to collect data from Apple pencil input, I tried to export the data into a CSV file. But so far, I only managed to create a single column which records the time length. I want to add another column to record the force from the Apple pencil.
This is what I have tried to do:
var patientsData:[Dictionary<String, AnyObject>] = Array()
var dct = Dictionary<String, AnyObject>()
// MARK: CSV writing
func createCSVX(from recArray:[Dictionary<String, AnyObject>]) {
var csvString = "\("Time")\n"
dct.updateValue(TestDraw.time as AnyObject, forKey: "T")
csvString = csvString.appending("\(String(describing: dct["T"]))\n")
patientsData.append(dct)
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
let fileURL = path.appendingPathComponent("TrailTime.csv")
try csvString.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("error creating file")
}
}
I know I can write another function to create another CSV file with a single column to record the force, but I would like to record them in a single spreadsheet​.
Also, does anyone know how to remove the "Optional" in the CSV file created?
This is what I have tried based on one of the answers.
func createCSVX(from recArray:[Dictionary<String, AnyObject>]) {
var csvString = "\("Time"),\("Force")\n"
dct.updateValue(TestDraw.time as AnyObject, forKey: "T")
dct.updateValue(TestDraw.force as AnyObject, forKey: "F")
patientsData.append(dct)
csvString = csvString.appending("\(String(describing: dct["T"])), \(String(describing: dct["F"]))\n")
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil , create: false )
let fileURL = path.appendingPathComponent("TrailTime.csv")
try csvString.write(to: fileURL, atomically: true , encoding: .utf8)
} catch {
print("error creating file")
}
print(TestDraw.force)
}
Tutorial copied from https://iostutorialjunction.com/2018/01/create-csv-file-in-swift-programmatically.html:
Step 1:
Create an array, named as "employeeArray" which will store all our records for the employees as key value objects. Also we will add dummy data to the newly created array
class ViewController: UIViewController {
var employeeArray:[Dictionary<String, AnyObject>] = Array()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
for i in 1...10 {
var dct = Dictionary<String, AnyObject>()
dct.updateValue(i as AnyObject, forKey: "EmpID")
dct.updateValue("NameForEmplyee id = \(i)" as AnyObject, forKey: "EmpName")
employeeArray.append(dct)
}
}
}
Step 2: Now we have data with us, and its time to create CSV(comma separated values) file using swift programmatically. For this we will loop through our records in "employeeArray" and append them in a string. Then we will write this string to our document directory of the app. All the stuff goes in different function named as "createCSV", below is the code for the same
func createCSV(from recArray:[Dictionary<String, AnyObject>]) {
var csvString = "\("Employee ID"),\("Employee Name")\n\n"
for dct in recArray {
csvString = csvString.appending("\(String(describing: dct["EmpID"]!)) ,\(String(describing: dct["EmpName"]!))\n")
}
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
let fileURL = path.appendingPathComponent("CSVRec.csv")
try csvString.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("error creating file")
}
}
Step 3: Finally we will call our function from "viewDidLoad". Below is the complete code
class ViewController: UIViewController {
var employeeArray:[Dictionary<String, AnyObject>] = Array()
override func viewDidLoad() {
super.viewDidLoad()
for i in 1...10 {
var dct = Dictionary<String, AnyObject>()
dct.updateValue(i as AnyObject, forKey: "EmpID")
dct.updateValue("NameForEmplyee id = \(i)" as AnyObject, forKey: "EmpName")
employeeArray.append(dct)
}
createCSV(from: employeeArray)
}
func createCSV(from recArray:[Dictionary<String, AnyObject>]) {
var csvString = "\("Employee ID"),\("Employee Name")\n\n"
for dct in recArray {
csvString = csvString.appending("\(String(describing: dct["EmpID"]!)) ,\(String(describing: dct["EmpName"]!))\n")
}
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
let fileURL = path.appendingPathComponent("CSVRec.csv")
try csvString.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("error creating file")
}
}
}
Excellent answer above, made a slight modification to address specific instances in the data. You can modify individual components as needed and remove commas, trim UUIDs, etc. Note this solution uses transactions stored in a list of Core Data objects. I also print the location of the data file so you can check it in the simulator.
func createCSVFile() {
var csvString = "id,name,description,category,date,type,receipt,amount\n"
for trans in transactions {
let transID = trans.id!.debugDescription.split(separator: "-")[0].replacingOccurrences(of: ")", with: "")
let transName = trans.name!
let transDesc = trans.desc!.replacingOccurrences(of: ",", with: "-")
let transCat = trans.category!
let transDate = trans.date!
let transType = trans.type!
var transReceipt = "None"
if trans.receipt == nil {
transReceipt = "Present"
}
let transAmount = trans.amount
let dataString = "\(transID),\(transName),\(transDesc),\(transCat),\(transDate),\(transType),\(transReceipt),\(transAmount)\n"
print("DATA: \(dataString)")
csvString = csvString.appending(dataString)
}
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
print("PATH: \(path)")
let fileURL = path.appendingPathComponent("CSVData.csv")
try csvString.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("error creating file")
}
}

Is it possible to output userinput to a .csv using swift?

I am very new to learning to code in Swift. I am trying to make an application that keeps a list of people who are coming in. I want it to log the name they input, time of visit, and the nature of their visit. However, I want this to be able to be exported to a program like Numbers or Excel. I have found some info on storing the inputs from the user but those seem to get deleted if the app is closed. I can't seem to find any other info, but perhaps I'm just searching the wrong info. Any help or guidance is appreciated.
Just store your log in a Array and then with this function you safe it to a .csv file.
func saveCSV(_ name : String,_ customUrl : URL) -> Bool {
let fileName = "\(name).csv"
let b = customUrl.appendingPathComponent(fileName)
var csvText = ""
var id = "1"
var name = "test"
csvText = "ID,Name\n"
let newLine = "\(id),\(name))\n"
csvText.append(newLine)
//or create a loop
// Task is my custom Struct
var array : [Task]
for task in customArray {
let newLine = "\(task.ean),\(task.menge),\(task.name)\n"
csvText.append(newLine)
}
do {
try csvText.write(to: b, atomically: true, encoding: String.Encoding.utf8)
return true
} catch {
print("Failed to create file")
print("\(error)")
return false
}
}
func createDic()->URL?{
let documentsPath1 = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
// CSV is the folder name
let logsPath = documentsPath1.appendingPathComponent("CSV")
do {
try FileManager.default.createDirectory(atPath: logsPath!.path, withIntermediateDirectories: true, attributes: nil)
return logsPath
} catch let error as NSError {
NSLog("Unable to create directory \(error.debugDescription)")
}
return nil
}
var customUrl = createDic()
and now you can call it :
if saveCSV(userList, customUrl){
print("success")
}
and after this you can do what you want with the .csv file

Swift FMDB Code Explanation

I am a beginner to Swift and FMDB, I got the code below from resources in the internet, and tried my best to understand the code. I have put comments below statements stating what I think it is doing. The ones with question marks I do not understand.
class ViewController: UIViewController {
#IBOutlet weak var name: UITextField!
#IBOutlet weak var specialty: UITextField!
//Defines name and specialty as contents of text fields
var dbpath = String()
//defines the database path
func getPath(fileName: String) -> String {
let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
//finds document and returns an array of paths
let fileURL = documentsURL.URLByAppendingPathComponent(fileName)
print(fileName)
//finds path to fileName with URLByAppendingPathComponent
print("File Path Is : \(fileURL)")
return fileURL.path!
//returns the fileURL in path format?????
}
//Button "Add Shop" definition
override func viewDidLoad() {
super.viewDidLoad()
let dirPaths =
NSSearchPathForDirectoriesInDomains(.DocumentDirectory,
.UserDomainMask, true)
//creates search paths for directories, then ?????
let docsDir = dirPaths[0]
let dbPath: String = getPath("shopdata.db")
//assigns string "shopdata.db" to dbPath
let fileManager = NSFileManager.defaultManager()
//easier access for NSFileManager, returns shared file for the process when called
if !fileManager.fileExistsAtPath(dbPath as String) {
//if there is already a database, do the following
let contactDB = FMDatabase(path: dbPath as String)
//contact database with path identified in function getPath
if contactDB == nil {
print("Error: \(contactDB.lastErrorMessage())")
//If there is no database
}
if contactDB.open() {
let sql_stmt = "CREATE TABLE IF NOT EXISTS CONTACTS (ID INTEGER PRIMARY KEY AUTOINCREMENT, SPECIALTY TEXT, NAME TEXT)"
if !contactDB.executeStatements(sql_stmt)
//executes a create table statement as defined above
{
print("Error: \(contactDB.lastErrorMessage())")
//if cannot execute statement, display error from fmdb
}
contactDB.close()
//close connection
} else {
print("Error: \(contactDB.lastErrorMessage())")
//if contact cannot be made, display error from fmdb
}
}
}
#IBAction func addShop(sender: AnyObject) {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This Function will get the file path of the give fileName from DocumentDirectory and return it back.
func getPath(fileName: String) -> String {
let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
//finds document and returns an array of paths
let fileURL = documentsURL.URLByAppendingPathComponent(fileName)
print(fileName)
//finds path to fileName with URLByAppendingPathComponent
print("File Path Is : \(fileURL)")
return fileURL.path!
//returns the fileURL in path format?????
}
And these line of code is not needed in here at all. This code also get the file path from DocumentDirectory of the application. Which is done in the getPath: function.
let dirPaths =
NSSearchPathForDirectoriesInDomains(.DocumentDirectory,
.UserDomainMask, true)
//creates search paths for directories, then ?????
let docsDir = dirPaths[0]
DocumentDirectory is where the application save the database.
Sorry for bad English. Hope it helps :)

An efficient way of writing to file, swift

I'm getting data from sensors using bluetooth, I want to append the string of data I get to the end of file.
When I tried the regular approach
if let dir = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first {
let path = NSURL(fileURLWithPath: dir).URLByAppendingPathComponent(self.file)
do {
try text.writeToURL(path, atomically: false, encoding: NSUTF8StringEncoding)
}
catch {/* error handling here */}
My app started to slow down until even labels were not updating anymore.
Tried using dispatch_async to do in background thread but still it was slowing down my app.
What approach should I use? I read sth about stream but failed to find some solutions in swift I could rely on
Probably your bluetooth is reading data faster than you are performing your file operations. You can optimize it by appending the text to the file instead of reading all the content on each write operation. You could also reuse the file handler between writes and keep the file open.
This sample is extracted from this answer:
struct MyStreamer: OutputStreamType {
lazy var fileHandle: NSFileHandle? = {
let fileHandle = NSFileHandle(forWritingAtPath: self.logPath)
return fileHandle
}()
lazy var logPath: String = {
let path : NSString = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first!
let filePath = (path as NSString).stringByAppendingPathComponent("log.txt")
if !NSFileManager.defaultManager().fileExistsAtPath(filePath) {
NSFileManager.defaultManager().createFileAtPath(filePath, contents: nil, attributes: nil)
}
print(filePath)
return filePath
}()
mutating func write(string: String) {
print(fileHandle)
fileHandle?.seekToEndOfFile()
fileHandle?.writeData(string.dataUsingEncoding(NSUTF8StringEncoding)!)
}
}
Then, you can create a single streamer and reuse it in different writes:
var myStream = MyStreamer()
myStream.write("First of all")
myStream.write("Then after")
myStream.write("And, finally")
In this case, you have the bonus that MyStreamer is also a OutputStreamType, so you can use it like this:
var myStream = MyStreamer()
print("First of all", toStream: &myStream )
print("Then after", toStream: &myStream)
print("And, finally", toStream: &myStream)
Finally I'd recommend you to move 'log.txt' string to a instance variable and pass it as a constructor parameter:
var myStream = MyStreamer("log.txt")
More info about file handler in the Apple Docs.
update #redent84's, to work in Swift 5
Code:
struct MyStreamer{
lazy var fileHandle = FileHandle(forWritingAtPath: logPath)
lazy var logPath: String = {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .allDomainsMask, true)[0]
let filePath = path + "/log.txt"
if FileManager.default.fileExists(atPath: filePath) == false{
FileManager.default.createFile(atPath: filePath, contents: nil, attributes: nil)
}
print(filePath)
return filePath
}()
mutating func write(_ string: String) {
print(fileHandle?.description ?? "呵呵")
fileHandle?.seekToEndOfFile()
if let data = string.data(using: String.Encoding.utf8){
fileHandle?.write(data)
}
}
}
Usage:
var myStream = MyStreamer()
myStream.write("First of all")
myStream.write("Then after")
myStream.write("And, finally")
try to write file like this..
var paths: [AnyObject] = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let filePath = paths[0].stringByAppendingString("/filename.mov")
do
{
try NSFileManager.defaultManager().removeItemAtURL(outputURL)
}
catch
{
error as NSError
}
do {
try text.writeToURL(path, atomically: false, encoding: NSUTF8StringEncoding)
}
I mean to say at last is you have to remove first.. If any query you can ask me

Get All folder names from a path in documents directory swift

I am trying to get a [String] that contains the names of all the folders in a folder i have created in the documents directory. I currently have something working but it is iterating everything in those directories too and giving me the names of the files.
Currently i have this:
let file_manager = NSFileManager.defaultManager()
let enumerator:NSDirectoryEnumerator? = file_manager.enumeratorAtPath(getDocumentsFilePath("Test"))
while let element = enumerator?.nextObject() as? String
{
// Add folder names to the return value
return_value.insert(element, atIndex: return_value.count)
}
but as i said this is giving me the names of files in the directories too.
I found this code which seems to be what I'm looking for but it seems to work off a NSParentSearchPathDirectory, but this seems like it would give me all directory names in the documents directory. Wondering if this is what I'm looking for but if so how can i convert my string (path to the folder i want directory names from) to an NSParentSearchPathDirectory. Or if my first example is correct how would i only make it return directories and not file names too.
NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory NSSearchPathDomainMask.AllDomainsMask, true) as? [String]
Here is a simple function that you can use to get paths of all contents in certain directory.
func contentsOfDirectoryAtPath(path: String) -> [String]? {
guard let paths = try? NSFileManager.defaultManager().contentsOfDirectoryAtPath(path) else { return nil}
return paths.map { aContent in (path as NSString).stringByAppendingPathComponent(aContent)}
}
let searchPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last!
let allContents = contentsOfDirectoryAtPath(searchPath)
Here is another way without the string operations:
var subdirs = [NSURL]()
let enumerator = NSFileManager.defaultManager().enumeratorAtURL(
NSURL.init(fileURLWithPath: "/System/Library", isDirectory: true),
includingPropertiesForKeys: [NSURLIsDirectoryKey],
options: .SkipsSubdirectoryDescendants,
errorHandler: nil)
while let url = enumerator?.nextObject() as? NSURL {
do {
var resourceValue: AnyObject?
try url.getResourceValue(&resourceValue, forKey: NSURLIsDirectoryKey)
if let isDirectory = resourceValue as? Bool where isDirectory == true {
subdirs.append(url)
}
}
catch let error as NSError {
print("Error: ", error.localizedDescription)
}
}
In Swift 4.0
func contentsOfDirectoryAtPath(path: String) -> [String]? {
guard let paths = try? FileManager.default.contentsOfDirectory(atPath: path) else { return nil}
return paths.map { aContent in (path as NSString).appendingPathComponent(aContent)}
}
func getListOfDirectories()->[String]?{
let searchPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last!
let allContents = contentsOfDirectoryAtPath(path: searchPath)
return allContents
}

Resources