Home > database >  Export Array using fileExporter
Export Array using fileExporter

Time:08-14

I am loading in a json file and creating an array. When a button is clicked, additional data is inserted into the array. What I want to do is export the modified array to a file. So essentially the array that has new data inserted into it.

What I'm not sure about is whether it is possible when exporting data from an array? or maybe I am going about this the wrong way?

EDIT: I don't necessarily want to export a json file, that was just the file type I first tried. I would be happy to export text files or csv's

ContentView

import SwiftUI
import UniformTypeIdentifiers


struct ContentView: View {
    @State private var name = ""
    @FocusState private var nameIsFocused: Bool
    @State var labels: [LabelData] = []
    @State var index = 0
    @State var saveFile = false


    var body: some View {
        HStack {
            Button(action: {
                index  = 1
                
                    if index <= labels.count {
                        labels[index - 1]._label = "Yellow" }
                }) {
                    Text("Y")
                }
            Button(action: {
                saveFile.toggle()
                //print(labels[index - 1])
                }) {
                    Text("Export")
                        .frame(width: 100, height: 100)
                        .foregroundColor(Color(red: 0.362, green: 0.564, blue: 1))
                        .background(Color(red: 0.849, green: 0.849, blue: 0.849))
                        .clipShape(RoundedRectangle(cornerRadius: 25.0, style: .continuous))
                }
            .offset(x: 0, y: 0)
            .fileExporter(isPresented: $saveFile, document: Doc(url: Bundle.main.path(forResource: "labeldata", ofType: "json")!), contentType: .json) { (res) in
                
                do {
                    let fileUrl = try res.get()
                    print(fileUrl)
                }
                catch {
                    print("cannot save doc")
                    print(error.localizedDescription)
                }
            }
        }
        VStack{
                VStack {
                    if index < labels.count{
                        if let test = labels[index] {
                    Text(test._name)
                        }}}
                .offset(x: 0, y: -250)
                .frame(
                    minWidth: 0,
                    maxWidth: 325
                )
                VStack {
                    if index < labels.count{
                        if let test = labels[index] {
                    Text(test._name)
                        }}}
                .offset(x: 0, y: -150)
                .frame(
                    minWidth: 0,
                    maxWidth: 325
                )
                VStack {
                    if index < labels.count{
                        if let test = labels[index] {
                    Text(test._label)
                        }}}
                .offset(x: 0, y: -50)
                .frame(
                    minWidth: 0,
                    maxWidth: 325
                )
        }
        .onAppear {
            labels = load("labeldata.json")
        }
    }
}

struct Doc : FileDocument {
    var url : String
    static var readableContentTypes: [UTType]{[.json]}
    init(url : String) {
        self.url = url
    }
    init(configuration: ReadConfiguration) throws {
        url = ""
    }
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let file = try! FileWrapper(url: URL(fileURLWithPath: url), options: .immediate)
        return file
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
        }
}

LabelData

import Foundation

struct LabelData: Codable {

    var _id: Int
    var _name: String
    var _type: String
    var _description: String
    var _label: String
}

labeldata.json

[
    {
        "_id" : 1,
        "_name" : "Label1",
        "_type" : "type1",
        "_description" : "description1",
        "_label" : ""
    },
    {
        "_id" : 2,
        "_name" : "Label2",
        "_type" : "type2",
        "_description" : "description2",
        "_label" : ""
    }
]

DataLoader

import Foundation

func load<T: Decodable>(_ filename: String) -> T {
    let data: Data
    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
        else {
            fatalError("Couldn't find \(filename) in main bundle.")
    }
    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }
    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}

CodePudding user response:

It sounds like you want to create a new JSON file from the modified data within the array. It is a bit unusual to want to create a new JSON file. Maybe you want to persist the data? In that case you wouldn't save it as JSON you would persist it with a proper DB (DataBase) like CoreData, FireBase, Realm, or ect...

But if you really want to do this. Then you need to create a new JSON file from the data in the array. You have a load<T: Decodable> function but you are going to want a save<T: Codable> function. Most people would, once again, use this opportunity to save the data to a DB.

This guys does a pretty good job explaining this: Save json to CoreData as String and use the String to create array of objects

So here is a good example of saving JSON data to a file:

let jsonString = "{\"location\": \"the moon\"}"

if let documentDirectory = FileManager.default.urls(for: .documentDirectory,
                                                    in: .userDomainMask).first {
    let pathWithFilename = documentDirectory.appendingPathComponent("myJsonString.json")
    do {
        try jsonString.write(to: pathWithFilename,
                             atomically: true,
                             encoding: .utf8)
    } catch {
        // Handle error
    }
}

CodePudding user response:

The fileExporter writes in-memory data to location selected by a user, so we need to create document with our content and generate FileWrapper from content to exported data (CSV in this example).

So main parts, at first, exporter:

.fileExporter(isPresented: $saveFile, 
                 document: Doc(content: labels), // << document from content !!
              contentType: .plainText) {

and at second, document:

struct Doc: FileDocument {

    static var readableContentTypes: [UTType] { [.plainText] }

    private var content: [LabelData]
    init(content: [LabelData]) {
        self.content = content
    }

    // ...

    // simple wrapper, w/o WriteConfiguration multi types or
    // existing file selected handling (it is up to you)
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let text = content.reduce("") {
            $0   "\($1._id),\($1._name),\($1._type),\($1._description),\($1._label)\n"
        }
        return FileWrapper(regularFileWithContents:
                 text.data(using: .utf8) ?? Data()) // << here !! 
    }

Tested with Xcode 13.4 / iOS 15.5

Test module is here

  • Related