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
Related
I created a stop button that can collect data, which will be saved to the defined path after clicking the stop button. However, if I want to continue collecting after clicking the stop button, the data will be added to the original text file. (This makes senses as I only know how to define one path)
My question is: Would it be possible to ask the user and input a new file name and save as a new text file after each stop so that the data is not added to the original file?
Below is what I have for one defined path and stacking up the data:
#IBAction func stopbuttonTapped(_ btn: UIButton) {
do {
let username:String = user_name.text!
fpsTimer.invalidate() //turn off the timer
let capdata = captureData.map{$0.verticesFormatted}.joined(separator:"") //convert capture data to string
let dir: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last! as URL
let url = dir.appendingPathComponent("testing.txt") //name the file
try capdata.appendLineToURL(fileURL: url as URL)
let result = try String(contentsOf: url as URL, encoding: String.Encoding.utf8)
}
catch {
print("Could not write to file")
}
}
And the extension I use for string and data:
extension String {
func appendLineToURL(fileURL: URL) throws {
try (self).appendToURL(fileURL: fileURL)
}
func appendToURL(fileURL: URL) throws {
let data = self.data(using: String.Encoding.utf8)!
try data.append(fileURL: fileURL)
}
func trim() -> String
{
return self.trimmingCharacters(in: CharacterSet.whitespaces)
}
}
extension Data {
func append(fileURL: URL) throws {
if let fileHandle = FileHandle(forWritingAtPath: fileURL.path) {
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.write(self)
}
else {
try write(to: fileURL, options: .atomic)
}
}
}
Do I need to set a default file name (maybe texting.txt) and then popped up a user input for saving the text file? (That's where I am not too sure how to integrate to what I already have). I thank you so much for your time and suggestions in advance.
You could generate unique names.
For example:
let url = dir.appendingPathComponent("testing-\(Date.timeIntervalSinceReferenceDate).txt")
or
let url = dir.appendingPathComponent("testing-\(UUID().uuidString).txt")
I'd like to know how could I create a URL from a path String.
Here my code:
let completePath = "/Volumes/MyNetworkFolder/"
do {
let items = try FileManager.default.contentsOfDirectory(atPath: completePath)
for item in items {
if item.hasDirectoryPath { //String has no member hasDirectoryPath
itemList.append(item)
}
}
} catch {
print("Failed to read dir")
let buttonPushed = dialogOKCancel(question: "Failed to read dir", text: "Map the network folder")
if(buttonPushed) {
exit(0)
}
}
I'd like to add only folders to the itemList array. The hasDirectoryPath is an URL method.
How could i change my code to get URLs not String.
Thank you in advance for any help you can provide.
Better use the contentsOfDirectory(at url: URL, ...) method of
FileManager, that gives you an array of URLs instead of
strings:
let dirPath = "/Volumes/MyNetworkFolder/"
let dirURL = URL(fileURLWithPath: dirPath)
do {
let items = try FileManager.default.contentsOfDirectory(at: dirURL,
includingPropertiesForKeys: nil)
for item in items {
if item.hasDirectoryPath {
// item is a URL
// item.path is its file path as a String
// ...
}
}
} catch {
print("Failed to read dir:", error.localizedDescription)
}
I have created save button which I guess going to create a .csv file to store x,y and name of the object on screen since I can't check it because I can't access the file its created
#IBAction func saveButton(_ sender: Any) {
let objectsName = stringObjectName.joined(separator: ",")
let coX = stringObjectX.joined(separator: ",")
let coY = stringObjectY.joined(separator: ",")
let fileName = getDocumentsDirectory().appendingPathComponent("output.csv")
CSVFilesName.append(fileName)
var csvText = "Name, Data X, Data Y\n"
let count = stringObjectName.count
if count > 0 {
for _ in 0..<(stringObjectName.count-1) {
let newLine = "\(objectsName),\(coX),\(coY)\n"
csvText.append(contentsOf: newLine)
}
}
do {
try csvText.write(to: fileName, atomically: true, encoding: String.Encoding.utf8)
} catch {
print("error")
}
print(fileName)
}
after this I try to access the file call "output.csv" which it is suppose to store in document directory in another viewport. So, I created the method to pass the method from another view controller to current view controller by using and store CSVFile
var CSVFileName = [URL]()
func assignArray() {
let cameraVC = CameraViewController()
CSVFileName = cameraVC.CSVFilesName
print(CSVFileName)
}
and the problem start here since I have array which suppose to store file name in String
let fileNames = ["sth1.csv", "sth2.csv"]
but I can't find the convert CSVFileName from saving button to String, and replace "static array" into "dynamic array" and change the method below to get
URL from FileManager().default.url instead of fileURL to give TableViewController data to show and accessible
private func prepareFileURLS() {
for file in fileNames {
let fileParts = file.components(separatedBy: ".")
print(fileParts[0])
if let fileURL = Bundle.main.url(forResource: fileParts[0], withExtension: fileParts[1]) {
if FileManager.default.fileExists(atPath: fileURL.path) {
print(fileURL)
fileURLs.append(fileURL as NSURL)
}
}
}
print(fileURLs)
}
Here is the way you can read Your CSV file :
func filterMenuCsvData()
{
do {
// This solution assumes you've got the file in your bundle
if let path = Bundle.main.path(forResource: "products_category", ofType: "csv"){
// STORE CONTENT OF FILE IN VARIABLE
let data = try String(contentsOfFile:path, encoding: String.Encoding.utf8)
var rows : [String] = []
var readData = [String]()
rows = data.components(separatedBy: "\n")
for data in 0..<rows.count - 1{
if data == 0 || rows[data].contains(""){
continue
}
readData = rows[data].components(separatedBy: ",")
Category.append(readData[0])
if readData[2] != ""{
Occassions.append(readData[2])
}
selectedOccassionsRadioButtonIndex = Array(repeating: false, count: Occassions.count)
selectedCategoryRadioButtonIndex = Array(repeating: false, count: Category.count)
}
}
} catch let err as NSError {
// do something with Error}
print(err)
}
}
I retrieve translation from a remote server and save this in
Application\ Support/Translation/Translation.plist
What I basically want to do in my app is use something like
translate(input: "hello")
In order to translate hello to the translation that is saved in my plist file. I created a function but I always get nil when reading the contents. Anyone who knows what I am doing wrong?
import Foundation
open class Translations {
static func translate(input: String) -> String {
var translations: [String: String] = [:] //Translation data
let documentsDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
let directoryURL = documentsDirectory?.appendingPathComponent("Translation")
let file = directoryURL?.appendingPathComponent("Translation").appendingPathExtension("plist")
if let plistXML = FileManager.default.contents(atPath: (file?.absoluteString)!) {
do {//convert the data to a dictionary and handle errors.
translations = try PropertyListSerialization.propertyList(from: plistXML, options: [], format: nil) as! [String:String]
} catch {
print("Error reading plist: \(error)")
}
}
guard let translation = translations[input] else {
return input
}
return translation
}
}
You are reading from the Documents directory, where as you say earlier in your post your file does not reside in. Instead it resides in the Application Support directory.
Try to make sure you are saving and reading from the same location.
I would also recommend using an extension to String to make translating easier, like so:
extension String {
var translated: String {
return Translation.default?.translate(self) ?? self
}
}
Then you can simply do:
"SomeText".translated
This is how I'd implement translations:
public final class Translation {
static let `default`: Translation? = Translation()
let translations: [String: String]
init?() {
guard let documentsURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
return nil
}
let translationURL = documentsURL.appendingPathComponent("Translation").appendingPathComponent("Translation").appendingPathExtension("plist")
do {
let data = try Data(contentsOfURL: translationURL)
let propertyList = try PropertyListSerialization(from: data, options: [], format: nil)
if let list = propertyList as? [String: String] {
translations = list
} else {
return nil
}
} catch {
// Handle error
return nil
}
}
func translate(_ input: String) -> String {
guard let translated = translations[input] else {
return input
}
return translated
}
}
This has the advantage that you're not reading the propertyList every time from disk you want to run a translation. Keep in mind that this implementation of mine does not provide any support for refreshing the data once the app is running.
Alternatively you could move the init code to a separate method, and removing the nullability of the init method. Then whenever a new propertyList is downloaded you could simply call -refresh() or whatever you want really.
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