swiftuiutiswiftui-fileimporter

SwiftUI .fileImporter with Uniform Type Identifier throws error


I am attempting to make a simple .fileImporter to work for a file where I define my own Uniform Type Identifier. For some reason, when I want to upload a file, I always receive the error

keyNotFound(CodingKeys(stringValue: "id", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "id", intValue: nil) ("id").", underlyingError: nil))

The interesting thing is that the data is actually available and seems to be correct as I can see when I convert the imported json to a string:

Optional("{"testData":{"name":"name","id":"D4C4C9CC-B7A0-4F79-8803-BF8C720396FF","number":10}}")

Since TestData is defined as Codable, encoding and decoding conformance is already built in. But for some reason the decoding does not seem to work. It also does not work when I write my own codable functions (not included in the following code).

Here is the entire code sample:

import CoreTransferable
import SwiftUI
import UniformTypeIdentifiers

struct ContentView: View {
@State private var showImport = false

    var body: some View {
        NavigationStack {
            ShareLink(item: ExportHandler(testData: TestData(id: UUID().uuidString,name: "name", number: 10)), preview: SharePreview("Share data."))
            .toolbar {
                Button {
                    showImport.toggle()
                } label: {
                    Label("Import Course", systemImage: "square.and.arrow.down")
                }
            }
            .fileImporter(isPresented: $showImport, allowedContentTypes: [.srtExportType]) { result in
                print("Result: \(result)")

                switch result {
                case .success(let success):
                    do {
                        if success.startAccessingSecurityScopedResource() {
                            let data = try Data(contentsOf: success)
                            let str = String(data: data, encoding: .utf8)
                            print("The data is available:", str)
                            let decodedParcours = try JSONDecoder().decode(TestData.self, from: data)
                        }
                        success.stopAccessingSecurityScopedResource()
                    } catch {
                        print("Error opening file: \(error)")
                    }
                case .failure(let failure):
                    print("Failure to open file: \(failure)")
                }
            }
        }
    }
}

struct TestData: Identifiable, Codable {
    var id: String
    var name: String
    var number: Int

    init(id: String, name: String, number: Int) {
        self.id = id
        self.name = name
        self.number = number
    }
}

struct ExportHandler: Codable, Transferable {
    var testData: TestData
    static var transferRepresentation: some TransferRepresentation {
        CodableRepresentation(contentType: .srtExportType)
            .suggestedFileName("File\(Date())")
    }
}

extension UTType {
    static var srtExportType = UTType(exportedAs: "ch.willowsrun.sharereceivetester")
}

I have also set up the necessary settings for the UTI:

enter image description here

What am I missing?


Solution

  • Note that in the ShareLink, you are sharing an instance of ExportHandler:

    ShareLink(item: ExportHandler(testData: ...))
    

    However, when you import the data, you try to decode an instance of TestData:

    let decodedParcours = try JSONDecoder().decode(TestData.self, from: data)
    

    This fails, because the JSON represents an instance of ExportHandler. You can see that it has a "testData" key.

    You should either do

    let decodedParcours = try JSONDecoder().decode(ExportHandler.self, from: data).testData
    

    Or make TestData conform to Transferable and share that directly. This in my opinion makes more sense. There is no point in having an ExportHandler if all it does is "to make the data Transferable". Just conform to Transferable directly.