3

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

2 Answers 2

2
+100

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

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

Comments

0

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

2 Comments

I actually don't really have a preference on the file type. In the code I tried a json but I would be happy with a simple text file or csv
How would you integrate your example into the current code?

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.