0

I develop iOS app and use fileExporter for exporting data to different text formats (csv, json etc). I moved "export" button view and models to separate files (struct and class) and got trouble when fileExporter does not appear. Seams like TextFileDocument is empty and I cant figure out why. Nothing happened after click on button "CSV". Thanks in advance for your ideas.

import SwiftUI
import UniformTypeIdentifiers

struct ExportButtonView: View {
    @ObservedObject private var vm: ExportButtonViewModel = ExportButtonViewModel()
    
    @State private var showingExporter: Bool = false
    @State private var showingChooseExportDataType: Bool = false
    
    var body: some View {
        Image(systemName: "square.and.arrow.up")
            .onTapGesture {
                showingChooseExportDataType.toggle()
            }
            .confirmationDialog("Choose data format to export", isPresented: $showingChooseExportDataType, titleVisibility: .visible) {
                Button("CSV") {
                    vm.initExportingTypeAndDocument(exportDataType: .csv)
                    showingExporter.toggle()
                }
                // Put here buttons for other formats
                .fileExporter(
                    isPresented: $showingExporter,
                    document: vm.exportingDocument,
                    contentType: .plainText,
                    defaultFilename: "data.\(vm.exportDataType)"
                ) { result in
                    switch result {
                    case .success(let url):
                        print("Saved to \(url)")
                    case .failure(let error):
                        print(error.localizedDescription)
                    }
                    
                }
            }
    }
}

class ExportButtonViewModel: ObservableObject {
    var exportDataType: TextFileDataExporterManager.ExportDataType = .csv
    var exportingDocument: TextFileDocument?
    
    func initExportingTypeAndDocument(exportDataType: TextFileDataExporterManager.ExportDataType){
        self.exportDataType = exportDataType
        self.exportingDocument = TextFileDocument(initialText: TextFileDataExporterManager(data: ["Test1", "Test2"], dataFormatType: exportDataType).getFormattedText())
    }
}

struct HomeView: View {
    var body: some View {
        ExportButtonView()
    }
}

protocol TextFileDataExporter {
    var data: [String] { get }
    func getFormattedText() -> String
}

class TextFileDataExporterManager: TextFileDataExporter {
    var data: [String]
    let dataFormatType: ExportDataType
    
    enum ExportDataType: String {
        case csv
    }
    
    init(data: [String], dataFormatType: ExportDataType) {
        self.data = data
        self.dataFormatType = dataFormatType
    }
    
    func getFormattedText() -> String {
        switch dataFormatType {
        case .csv:
            return CsvExporter(data: data).getFormattedText()
        }
    }
}

class CsvExporter: TextFileDataExporter {
    var data: [String]
    
    init(data: [String]) {
        self.data = data
    }
    
    func getFormattedText() -> String {
        return "Here;will;be;some;csv;data"
    }
}

struct TextFileDocument: FileDocument {
    // tell the system we support only plain text
    static var readableContentTypes = [UTType.plainText]

    // by default our document is empty
    var text = ""

    // a simple initializer that creates new, empty documents
    init(initialText: String = "") {
        text = initialText
    }

    // this initializer loads data that has been saved previously
    init(configuration: ReadConfiguration) throws {
        if let data = configuration.file.regularFileContents {
            text = String(decoding: data, as: UTF8.self)
        }
    }

    // this will be called when the system wants to write our data to disk
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = Data(text.utf8)
        return FileWrapper(regularFileWithContents: data)
    }
}

1 Answer 1

1

You need to move the .fileExporter modifier outside the confirmationDialog. Once you selected e.g. CSV the confirmationDialog is removed from hierarchy and the modifier doesn't have any effect.

struct ExportButtonView: View {
    @ObservedObject private var vm: ExportButtonViewModel = ExportButtonViewModel()

    @State private var showingExporter: Bool = false
    @State private var showingChooseExportDataType: Bool = false

    var body: some View {
        Image(systemName: "square.and.arrow.up")
            .onTapGesture {
                showingChooseExportDataType.toggle()
            }
            .confirmationDialog("Choose data format to export", isPresented: $showingChooseExportDataType, titleVisibility: .visible) {
                Button("CSV") {
                    vm.initExportingTypeAndDocument(exportDataType: .csv)
                    showingExporter.toggle()
                }
                // Put here buttons for other formats
            }
            .fileExporter(
                isPresented: $showingExporter,
                document: vm.exportingDocument,
                contentType: .plainText,
                defaultFilename: "data.\(vm.exportDataType)"
            ) { result in
                switch result {
                case .success(let url):
                    print("Saved to \(url)")
                case .failure(let error):
                    print(error.localizedDescription)
                }

            }
    }
}

Additionally you can check if the ContentType is already provided by the system. E.g. .commaSeparatedText.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.