I have an app that can export user input information in a csv file. I set the file name to be PatientInfo in the code. However when I receive new data and click save again. The old one will be overwritten. How to automatically create a new csv file with a name perhaps PatientInfo_01? Or even better, let the user type in the file name after clicking save button.
This is the original code I have:
var patientsData:[Dictionary<String, AnyObject>] = Array()
var dct = Dictionary<String, AnyObject>()
func createCSVX(from recArray:[Dictionary<String, AnyObject>]) {
var csvString = "\("Time"),\("Force")\n"
dct.updateValue(-TestDraw.time as AnyObject, forKey: "T")
for i in 0...TestDraw.force.count-1 {
dct.updateValue(TestDraw.force[i] 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("PatientInfo.csv")
try csvString.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("error creating file")
}
}
Try this:
var counter = 0
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
let fileName = String(format:"PatientInfo%d.csv",counter)
let fileURL = path.appendingPathComponent(fileName)
try csvString.write(to: fileURL, atomically: true, encoding: .utf8)
And in the next iteration, increase the counter value by 1.
Related
Whenever the user exports data (it is a JSON format) it creates a folder named "Documents." And does not directly export the file and it does this for both my JSON export and CSV.
I have tried changing the fileManager settings but nothing seems to work that I have tried and that includes changing the default, for, and in. The latter two being when I call fileManger.url
Here is my main export for my JSON
// MARK: - Export to Share
func toExportJSON() {
// Call to clear Chached exported files
clearAllFile()
var exportArrayJson = [exportJsonData]()
var exportArray = [Item]()
exportArray = fetchedRC.fetchedObjects!
for i in exportArray {
let newI = exportJsonData(name: i.name!, pricePer: i.pricePer, totalPrice: i.totalPrice!, isComplete: i.isComplete, Qty: i.quantity, Cat: i.catagory!, Priority: i.priority, DNH: i.didHave)
exportArrayJson.append(newI)
}
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.keyEncodingStrategy = .convertToSnakeCase
var jsonDataTop = Data()
do {
let jsonData = try encoder.encode(exportArrayJson)
jsonDataTop = jsonData
// Old Code to print for testing
///if let jsonString = String(data: jsonData, encoding: .utf8) {
///print(jsonString)
///print(jsonData)
///}
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
let fileURL = path.appendingPathComponent("\(detailedList.lname!).json")
try jsonDataTop.write(to: fileURL)//.write(to: fileURL)//write(to: fileURL, encoding: .utf8)
let vc = UIActivityViewController(activityItems: [path], applicationActivities: [])
present(vc, animated: true, completion: nil)
} catch {
print("error creating file")
}
} catch {
print(error.localizedDescription)
}
}
I expect this to export the JSON by its self and not in a folder. I want only the file so it is easier for the user to share.
No matter where you save the file, you should delete it when the UIActivityViewController is finished. You can set a completion handler to remove the file.
vc.completionWithItemsHandler = { (_, _, _, _) in
try? fileManager.removeItem(at: fileURL)
}
Another option would be to save the file in the temporaryDirectory instead of documents.
let fileURL = path
.temporaryDirectory
.appendingPathComponent("\(detailedList.lname!).json")
But you should still remove the file after.
This question has been asked quite a few times over the years, but it has changed again in Swift 5, particularly in the last two betas.
Reading a JSON file seems to be quite simple:
func readJSONFileData(_ fileName: String) -> Array<Dictionary<String, Any>> {
var resultArr: Array<Dictionary<String, Any>> = []
if let url = Bundle.main.url(forResource: "file", withExtension: "json") {
if let data = try? Data(contentsOf: url) {
print("Data raw: ", data)
if let json = try? (JSONSerialization.jsonObject(with: data, options: []) as! NSArray) {
print("JSON: ", json)
if let arr = json as? Array<Any> {
print("Array: ", arr)
resultArr = arr.map { $0 as! Dictionary<String, Any> }
}
}
}
}
return resultArr
}
But writing is incredibly difficult, and all of the previous methods found on this site have failed in Swift 5 on Xcode 11 betas 5 and 6.
How can I write data to a JSON file in Swift 5?
I tried these approaches:
How to save an array as a json file in Swift?
Writing JSON file programmatically swift
read/write local json file swift 4
There weren't any errors except for deprecation warnings, and when I fixed those, it simply didn't work.
Let’s assume for a second that you had some random collection (either arrays or dictionaries or some nested combination thereof):
let dictionary: [String: Any] = ["bar": "qux", "baz": 42]
Then you could save it as JSON in the “Application Support” directory like so:
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("example.json")
try JSONSerialization.data(withJSONObject: dictionary)
.write(to: fileURL)
} catch {
print(error)
}
For rationale why we now use “Application Support” directory rather than the “Documents” folder, see the iOS Storage Best Practices video or refer to the File System Programming Guide. But, regardless, we use those folders, not the Application’s “bundle” folder, which is read only.
And to read that JSON file:
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent("example.json")
let data = try Data(contentsOf: fileURL)
let dictionary = try JSONSerialization.jsonObject(with: data)
print(dictionary)
} catch {
print(error)
}
That having been said, we generally prefer to use strongly typed custom types rather than random dictionaries where the burden falls upon the programmer to make sure there aren’t typos in the key names. Anyway, we make these custom struct or class types conform to Codable:
struct Foo: Codable {
let bar: String
let baz: Int
}
Then we’d use JSONEncoder rather than the older JSONSerialization:
let foo = Foo(bar: "qux", baz: 42)
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("example.json")
try JSONEncoder().encode(foo)
.write(to: fileURL)
} catch {
print(error)
}
And to read that JSON file:
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent("example.json")
let data = try Data(contentsOf: fileURL)
let foo = try JSONDecoder().decode(Foo.self, from: data)
print(foo)
} catch {
print(error)
}
For more information about preparing JSON from custom types, see the Encoding and Decoding Custom Types article or the Using JSON with Custom Types sample code.
In case anyone is working with custom objects (as I am) and want a more 'all around' function with generics
Save File To Manager
func saveJSON<T: Codable>(named: String, object: T) {
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(named)
let encoder = try JSONEncoder().encode(object)
try encoder.write(to: fileURL)
} catch {
print("JSONSave error of \(error)")
}
}
Read File From Manager
func readJSON<T: Codable>(named: String, _ object: T.Type) -> T? {
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(named)
let data = try Data(contentsOf: fileURL)
let object = try JSONDecoder().decode(T.self, from: data)
return object
} catch {
return nil
}
}
Answering the question:
Let's just say I have some JSON data - how would I write it to
file.json? It's in my project directory - is this writable? And if
not, how would I make it writable?
Assuming that "some JSON" means an array of dictionaries as [<String, String>], here is a simple example of how you could do it:
Consider that we have the following array that we need to write it as JSON to a file:
let array = [["Greeting":"Hello", "id": "101", "fruit": "banana"],
["Greeting":"Hola", "id": "102", "fruit": "tomato"],
["Greeting":"Salam", "id": "103", "fruit": "Grape"]]
The first thing to do is to convert it to a JSON (as Data instance). There is more that one option to do it, let's use JSONSerialization one:
do {
let data = try JSONSerialization.data(withJSONObject: array, options: [])
if let dataString = String(data: data, encoding: .utf8) {
print(dataString)
}
} catch {
print(error)
}
At this point, you should see on the console:
[{"Greeting":"Hello","fruit":"banana","id":"101"},{"fruit":"tomato","Greeting":"Hola","id":"102"},{"fruit":"Grape","Greeting":"Salam","id":"103"}]
which is our data formatted as valid JSON.
Next, we need to write it on the file. In order to do it, we'll use FileManager as:
do {
let data = try JSONSerialization.data(withJSONObject: array, options: [])
if let documentDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let fileUrl = documentDirectoryUrl.appendingPathComponent("MyFile.json")
try data.write(to: fileUrl)
}
} catch {
print(error)
}
Note that the file should exist in the documents directory; In the above example its name should be "MyFile.json".
data coming from server is in ex.2035bytes I want to write this data in to file I am writing but data is not showing
Alamofire.request(url , method: .post, parameters: Parameters as? [String: Any] , encoding: URLEncoding.httpBody, headers: [
"Content-Type": "application/x-www-form-urlencoded"
]).responseData{ (response) in
print(response)
print(response.result.value!)
print(response.result.description)
guard let jsonData = response.result.value ,response.result.isSuccess else {
didFail(response.result.error!)
return
}
guard let id = ApplicantModel.shared.applicationId else {
return
}
let file = "application_\(id)" //this is the file. we will write to and read from it
let documentsPath1 = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
//let logsPath = documentsPath1.appendingPathComponent("f")
let fileURL = documentsPath1.appendingPathComponent("file")
let data: Data = response.result.value!
// print(logsPath)
//writing
do {
try FileManager.default.createDirectory(at: documentsPath1 as URL, withIntermediateDirectories: true, attributes: nil)
//try data.write(to: fileURL!, atomically: false, encoding: .utf8)
// try data.write(to: fileURL!, options: Data.WritingOptions.atomic)
try data.write(to: fileURL!) )
}
catch {/* error handling here */}
let json = JSON(jsonData)
didFinish(json)
}
here I am creating file and writing data in to it but its not showing
You can use this function to write data. Code is self explanatory, but I tried to make it more clear.
func writeToFile(data: Data, fileName: String){
// get path of directory
guard let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last else {
return
}
// create file url
let fileurl = directory.appendingPathComponent("\(fileName).txt")
// if file exists then write data
if FileManager.default.fileExists(atPath: fileurl.path) {
if let fileHandle = FileHandle(forWritingAtPath: fileurl.path) {
// seekToEndOfFile, writes data at the last of file(appends not override)
fileHandle.seekToEndOfFile()
fileHandle.write(data)
fileHandle.closeFile()
}
else {
print("Can't open file to write.")
}
}
else {
// if file does not exist write data for the first time
do{
try data.write(to: fileurl, options: .atomic)
}catch {
print("Unable to write in new file.")
}
}
}
You have a problem with creating the directory and naming the file you need to change it like this
let file = "application_\(id)"
let documentsPath1 = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let logsPath = documentsPath1.appendingPathComponent("file")
do
{
try FileManager.default.createDirectory(atPath: logsPath!.path, withIntermediateDirectories: true, attributes: nil)
let fileURL = logsPath?.appendingPathComponent(file)
try data.write(to: fileURL!)
}
catch let error as NSError
{
NSLog("Unable to create directory \(error.debugDescription)")
}
I think you create a wrong folder. It due to you can't save that file in your folder. You can look at my example. Which I create 1 folder to contain files.
Step1: Get document path
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
Step2: create a destination URL. Where you would like to store
let url = paths[0].appendingPathComponent(self.fileImagejpeg)
Step 3: Write data in files
try? data.write(to: url)
You can use try catch to get log when write failure
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")
}
}
I am creating new folder using createDirectory with below code.
*let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
// Get documents folder
let documentsDirectory: String = paths.first ?? ""
// Get your folder path
let dataPath = documentsDirectory + "/MyNewFolder"
print("Path\(dataPath)")
if !FileManager.default.fileExists(atPath: dataPath) {
// Creates that folder if no exists
try? FileManager.default.createDirectory(atPath: dataPath, withIntermediateDirectories: false, attributes: nil)
}*
Now i want to store new file like log.text under “MyNewFolder”. Can anybody suggest me how to save new files under “MyNewFolder” folder
Thanks in advance.
NSSearchPathForDirectoriesInDomains is outdated. The recommended API is the URL related API of FileManager
let folderName = "MyNewFolder"
let fileManager = FileManager.default
let documentsFolder = try! fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let folderURL = documentsFolder.appendingPathComponent(folderName)
let folderExists = (try? folderURL.checkResourceIsReachable()) ?? false
do {
if !folderExists {
try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: false)
}
let fileURL = folderURL.appendingPathComponent("log.txt")
let hello = Data("hello".utf8)
try hello.write(to: fileURL)
} catch { print(error) }
You are strongly discouraged from building paths by concatenating strings.
You can try
try? FileManager.default.createDirectory(atPath: dataPath, withIntermediateDirectories: false, attributes: nil)
do {
let sto = URL(fileURLWithPath: dataPath + "log.txt") // or let sto = URL(fileURLWithPath: dataPath + "/log.txt")
try Data("SomeValue".utf8).write(to: sto)
let read = try Data(contentsOf: sto)
print(String(data: read, encoding: .utf8)!)
}
catch {
print(error)
}
let filePath = dataPath + "/log.txt"
FileManager.default.createFile(filePath, contents:dataWithFileContents, attributes:nil)