通过 CSV 文件导出数据

Exporting Data Via CSV File

我有几个关于使用 CSV 文件的问题。首先,目前没有太多关于使用 CSV 文件的数据。是否正在使用其他格式?

我的具体问题与将数据移动到电子表格有关。如何防止包含逗号的数字和日期跨电子表格单元格拆分? Apple 更新了日期、货币和数字的 SwiftUI 格式化程序,使创建具有专业外观的视图变得更加容易,但它们在输出中插入了额外的逗号。包含逗号的数字在两个或多个电子表格单元格之间拆分,例如 1,600 在第一个单元格中放置 1,在下一个单元格中放置 600。

我最近看到有人建议在包含逗号的文本周围使用双引号以防止拆分两个或多个单元格,但它似乎不适用于 Numbers 和 Excel。我也试过从字符串中删除逗号:

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)
        }

这个方法的问题是,如果文本中有空格,它就不起作用了。我还有一个或多个在线模块 运行 从代码生成 CSV 文件。我不太了解如何使用这些。

下面的软件展示了在生成简单收据和 CSV 文件时使用一些新的格式化程序。如果您单击“On My iPhone”,然后单击“Move”,CSV 文件路径将出现在控制台 window 中。将路径拖到电子表格图标即可查看。


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)!)
    }
}

我认为这 CodableCSV project 对您来说将是一个很好的起点。

我修改的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)")
}

当我点击 'Export' 按钮时,我从打印语句中看到:

Quanity,Item,Cost,Total,Total (USD)
"1,600",ChocoBar,€4.95,"€7,920.00",",954.27"

您需要慎重添加 titlesubTitle"Sale in Dollars",因为它们所在的行需要与您的列数相同数据——CSV 在这方面不是 Excel;在 Excel 中,您可以将数据放入任何 单元格 中,没有强加的结构,例如:

let myRows = [
  ["Henry's Chocolate Sales Frankfurt", "", "", ""],  // 3 empty (placeholder) columns
  ...
]