I have several questions on using CSV files. First, there isn't much current data on using CSV files. Are there other formats that are being used instead?
My specific question has to do with moving data to a spreadsheet. How do you prevent numbers and dates containing commas from splitting across spreadsheet cells? Apple has updated the SwiftUI formatters for dates, currency, and numbers making it easier to create professional looking views but they insert additional commas into the output. Numbers containing commas are split between two or more spreadsheet cells such as 1,600 placing 1 in the first cell and 600 in the next cell.
I saw recently where someone suggested using double quotes around text containing commas to prevent splitting across two or more cells, but it doesn't appear to work with Numbers and Excel. I have also tried removing commas from strings:
var eDate = endDate.formatted(.dateTime.year().day().month(.wide))
//remove the comma in the date
if let i = sDate.firstIndex(of: ",") {
sDate.remove(at: i)
}
The problem with this method is that it doesn't work if there are spaces in the text. There are also one or more online modules that I have run across that generates a CSV file from code. I'm not too knowledgeable about using these.
The software below shows using some of the new formatters in the generation of a simple receipt and CSV file. If you click on "On My iPhone" and then "Move", the CSV file path will appear in the console window. Drag the path to a spreadsheet icon to view.
import SwiftUI
import UniformTypeIdentifiers
struct ContentView: View {
var myDate: Date = Date()
var barCount: Int = 1600
var barCost: Double = 4.95 // euors
var xRate: Double = 1.13059 // euro to dollar exchange rate
var dTotal: Double = 0.0
var usTotal: Double = 0.0
var newTotal: Double = 0.0
var barName: String = "ChocoBar"
var sTotal: String = ""
var sCount: String = ""
var sCost: String = ""
var body: some View {
VStack {
Text("Henry's Chocolate Sales Frankfurt")
.foregroundColor(.green)
.font(.title2)
.padding(.bottom, 20)
let sToday = myDate.formatted(.dateTime.year().day().month(.wide).weekday(.wide).hour().minute())
Text(sToday)
.padding(.bottom, 20)
Text("Quanity Item Cost Total")
.fontWeight(.bold)
HStack {
let sCount = barCount.formatted(.number)
Text(sCount)
Text(barName)
let sCost = barCost.formatted(.currency(code: "eur"))
Text(sCost)
let dTotal = Double(barCount) * barCost
let sTotal = dTotal.formatted(.currency(code: "eur"))
Text(sTotal)
}
HStack {
let usTotal = Double(barCount) * barCost * xRate
let newTotal = usTotal.formatted(.currency(code: "usd"))
Text("Sale in Dollars")
Text(newTotal)
}
.padding(.top, 20)
}
CreateCsvTable(myDate: myDate, barName: barName, barCount: barCount, barCost: barCost)
}
}
// copy receipt to csv file
struct CreateCsvTable: View {
let myDate: Date
let barName: String
let barCount: Int
let barCost: Double
@State private var showingExporter: Bool = false
@State private var document: MessageDocument?
var csvData: String = ""
var title = "\n,Henry's Chocolate Sales Frankfurt\n\n"
var subtitle = "Quanity,Item,Cost,Total\n"
var totalDollars = "\n\n,,,Sale in Dollars"
var xRate: Double = 1.13059 // euro to dollar exchange rate
var body: some View {
VStack {
Button ( action: {
showingExporter = true
document = createCsv(myDate: myDate, barName: barName, barCount: barCount, barCost: barCost)
}) {
HStack (alignment: .firstTextBaseline) {
Text("Export Receipt")
.fontWeight(.bold)
.font(.title3)
Image(systemName: "square.and.arrow.up")
}
.padding(.top, 20)
}
}.fileExporter(
isPresented: $showingExporter,
document: document,
contentType: .plainText,
defaultFilename: "Receipt.csv"
) { result in
switch result {
case .success(let url):
print("Saved to \(url)")
case .failure(let error):
print(error.localizedDescription)
}
}
}
// export data via csv file
func createCsv(myDate: Date, barName: String, barCount: Int, barCost: Double) -> (MessageDocument) {
let sToday = myDate.formatted(.dateTime.year().day().month(.wide).weekday(.wide).hour().minute())
let sCount = barCount.formatted(.number)
let sCost = barCost.formatted(.currency(code: "eur"))
let barTotal = barCost * Double( barCount)
let sTotal = barTotal.formatted(.currency(code: "eur"))
let usTotal = Double(barCount) * barCost * xRate
let newTotal = usTotal.formatted(.currency(code: "usd"))
let csvData = title sToday "\n\n" subtitle sCount "," barName "," sCost "," sTotal totalDollars "," newTotal
print(csvData)
return MessageDocument(message: csvData)
}
}
struct MessageDocument: FileDocument {
static var readableContentTypes: [UTType] { [.plainText] }
var message: String = ""
init(message: String) {
self.message = message
}
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
message = string
}
// this will be called when the system wants to write our data to disk
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
return FileWrapper(regularFileWithContents: message.data(using: .utf8)!)
}
}
CodePudding user response:
I think this CodableCSV project will be a really good starting point for you.
Here's the end of my modified createCsv()
:
let myRows = [
["Quanity", "Item", "Cost", "Total", "Total (USD)"],
[sCount, barName, sCost, sTotal, newTotal]
]
do {
let string = try CSVWriter.encode(rows: myRows, into: String.self)
print(string)
return MessageDocument(message: string)
} catch {
fatalError("Unexpected error encoding CSV: \(error)")
}
When I click on the 'Export' button, I see, from that print statement:
Quanity,Item,Cost,Total,Total (USD)
"1,600",ChocoBar,€4.95,"€7,920.00","$8,954.27"
You're going to need to be deliberate about adding title
, subTitle
, "Sale in Dollars"
because the rows they're on need to have the same number of columns as your data—CSV isn't Excel in this regard; where in Excel you can put data in any cell, no imposed structure—so something like:
let myRows = [
["Henry's Chocolate Sales Frankfurt", "", "", ""], // 3 empty (placeholder) columns
...
]