skip to Main Content

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


  1. 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
        }
    }
    
    Login or Signup to reply.
  2. 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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search