Problem with conformance to FileDocument: Outdated Tutorial, or just goof on my part? "Build document based apps in SwiftUI - WWDC20"

I got the following error when trying to do the same thing as in the tutorial.

// Document.swift

import SwiftUI
import UniformTypeIdentifiers


extension UTType {
    static let Test_ProjectDocument =
        UTType(exportedAs: "com.testproject")
}


struct Test_ProjectDocument: FileDocument, Codable {  // < "Type 'Test_ProjectDocument' 
                                                      // does not conform to protocol 
                                                      // 'FileDocument' "

                                                           // "Protocol requires initializer 'init(configuration)' with 
                                                           // type '(configuration: Test_ProjectDocument.ReadConfiguration)' 
                                                           // (aka '(configuration 
                                                           // FileDocumentReadConfiguration)') (SwiftUI.FileDocument.init)"
                                                           
                                                           // "Protocol requires function 'fileWrapper(configuration:)' 
                                                           // with type '(Test_ProjectDocument.WriteConfiguration) 
                                                           // throws -> FileWrapper' (aka 
                                                           // '(FileDocumentWriteConfiguration) throws -> FileWrapper') 
                                                           // (SwiftUI.FileDocument.fileWrapper) 
                                                           
                                                           // "Add stubs for conformance" 
                                                           // (What this changes is detailed below) 
    
    var allClasses: [SchoolClass]
    
    init() {
        self.allClasses = []
    }

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


    init(fileWrapper: FileWrapper, contentType: UTType) throws {
        let data = fileWrapper.regularFileContents!
        self = try JSONDecoder().decode(Self.self, from: data)
    }
    
    func write(to fileWrapper: inout FileWrapper, contentType: UTType) throws {
        let data = try JSONEncoder().encode(self)
        fileWrapper = FileWrapper(regularFileWithContents: data)
    }
    
}

(Rest of data model and app struct)

import SwiftUI
import CoreServices

public struct SchoolClass : Hashable, Codable, Identifiable {
    public var id: Int
    
    var ClassStudents: String = "Hello D. World" // placeholder 
    
}

import SwiftUI

@main
struct Test_ProjectApp: App {
    var body: some Scene {  // < "Type 'Test_ProjectDocument' does not conform to protocol 'FileDocument' "
        DocumentGroup(newDocument: Test_ProjectDocument()) { file in
            ContentView(document: file.$document)
        }
    }
}

This is what the suggested fix adds:

import SwiftUI
import UniformTypeIdentifiers


extension UTType {
    static let Test_ProjectDocument =
        UTType(exportedAs: "com.testproject")
}


struct Test_ProjectDocument: FileDocument, Codable {

//// Suggested fix 
    init(configuration: ReadConfiguration) throws {
        <#code#>
    }
    
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        <#code#>
    }
    ////
    
    
    var allClasses: [SchoolClass]
    
    init() {
        self.allClasses = []
    }

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


    init(fileWrapper: FileWrapper, contentType: UTType) throws {
        let data = fileWrapper.regularFileContents!
        self = try JSONDecoder().decode(Self.self, from: data)
    }
    
    func write(to fileWrapper: inout FileWrapper, contentType: UTType) throws {
        let data = try JSONEncoder().encode(self)
        fileWrapper = FileWrapper(regularFileWithContents: data)
    }
    
}

Does anyone know the syntax putting the JSON stuff in the suggested format, or a different solution?

And is the suggested fix ment to replace the other initializer, and write function, or be used at the same time?

There is an updated version of the project available: Building a document-based app with SwiftUI

1 Like

Thanks NotTheNHK. This tutorial uses the syntax "init(configuration: ReadConfigration) ..." instead of the stuff that doesn't work.

Unfortunately, unlike the original tutorial, this one does not detail how to put more than String in the document, by using something like JSON encoders, and decoders.

This is what that tutorial provides:

import SwiftUI
import UniformTypeIdentifiers

struct WritingAppDocument: FileDocument {

    // Define the document type this app loads.
    // - Tag: ContentType
    static var readableContentTypes: [UTType] { [.writingAppDocument] }
    

    var story: String  // < Would like to replace this with  'var storyChapters: [chapter]' 


    init(text: String = "") { // This would become: // init() { 
        self.story = text.                          //    self.storyChapters = [] 
    }                                               // }


    // Load a file's contents into the document.
    // - Tag: DocumentInit
    init(configuration: ReadConfiguration) throws { 
             // No idea what to replace the String stuff in here with // 
        guard let data = configuration.file.regularFileContents, 
              let string = String(data: data, encoding: .utf8)
        else {
            throw CocoaError(.fileReadCorruptFile)
        }
        story = string  
    }


    // Saves the document's data to a file.
    // - Tag: FileWrapper
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {      
            // No idea what to do instead of this String stuff either // 
        let data = Data(story.utf8)
        return .init(regularFileWithContents: data)
    }
}

Types conforming to the Encodable and/or Decodable protocol (or both, i.e, Codable) can be freely encoded and decoded to and from a separate format, see here for more information: Encoding and Decoding Custom Types. But, if you want to manually encode or decode to and from JSON, you would either use JSONEncoder or JSONDecoder:

struct Chapter: Codable {
  let id: Int
  let content: String
}

struct WritingAppDocument: FileDocument {
  static var readableContentTypes: [UTType] { [.writingAppDocument] }

  var storyChapters: [Chapter]

  init() {
    self.storyChapters = []
  }

  init(configuration: ReadConfiguration) throws {
    guard
      let data = configuration.file.regularFileContents
    else { throw CocoaError(.fileReadCorruptFile) }

    storyChapters = try JSONDecoder().decode([Chapter].self, from: data)
  }

  func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
    try FileWrapper(regularFileWithContents: JSONEncoder().encode(storyChapters))
  }
}

Note: Perhaps, I missed the Chapter declaration in your question, so I will use an example type here. Also, it is preferable to reuse the JSONEncoder/JSONDecoder instance instead of creating a new one for each operation.

2 Likes