TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: Create a new ReferenceFileDocument programatically on MacOS

Forums > SwiftUI

I am trying to create a new ReferenceFileDocument programatically in a SwiftUI app on MacOS using Ventura 13.5.1 and Xcode 14.3.1. To create a new document within a DocumentGroup, SwiftUI provides the NewDocumentAction in the Environment. But when I try to call the action with a new document, I receive the compiler error

Instance method 'callAsFunction' requires that 'MyDocument' conform to 'FileDocument'

According to the documentation, action assumes that you define a document that conforms to the FileDocument or ReferenceFileDocument protocol, and a DocumentGroup that handles the associated file type. In my sample I conform to ReferenceFileDocument.

Any ideas what is wrong? Thanks.

Sample code:

import SwiftUI
import UniformTypeIdentifiers

// DOCUMENT EXAMPLE

extension UTType {
    static var exampleText: UTType {
        UTType(importedAs: "com.example.plain-text")
    }
}

final class MyDocument: ReferenceFileDocument {
    typealias Snapshot = String

    // We add `Published` for automatic SwiftUI updates as
    // `ReferenceFileDocument` refines `ObservableObject`.
    @Published
    var number: Int

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

    init(number: Int = 42) {
        self.number = number
    }

    init(configuration: ReadConfiguration) throws {
        guard
            let data = configuration.file.regularFileContents,
            let string = String(data: data, encoding: .utf8),
            let number = Int(string)
        else {
            throw CocoaError(.fileReadCorruptFile)
        }
        self.number = number
    }

    func snapshot(contentType: UTType) throws -> String {
        "\(number)"
    }

    func fileWrapper(
        snapshot: String,
        configuration: WriteConfiguration
    ) throws -> FileWrapper {
        // For the sake of the example this force unwrapping is considered as safe.
        let data = snapshot.data(using: .utf8)!
        return FileWrapper(regularFileWithContents: data)
    }
}

// APP EXAMPLE FOR MACOS

@main
struct MyApp: App {
    var body: some Scene {
        DocumentGroup(
            newDocument: {
                MyDocument()
            },
            editor: { file in
                ContentView(document: file.document)
                    .frame(width: 400, height: 400)
            }
        )
    }
}

struct DocumentProxy {
    let documentController: NSDocumentController = .shared
    let undoManager: UndoManager?

    func registerUndo<Target>(
        withTarget target: Target,
        handler: @escaping (Target) -> Void
    ) where Target : AnyObject {
        if let undoManager = undoManager {
            undoManager.registerUndo(withTarget: target, handler: handler)
        }
    }

    func updateDocument() {
        // FIXME: `currentDocument` could return `nil` in certain situations.
        // Make this logic more robust.
        if let document = documentController.currentDocument {
            document.updateChangeCount(.changeDone)
        }
    }
}

struct DocumentReader<Content>: View where Content: View {
    @Environment(\.undoManager)
    var _undoManager: UndoManager?

    let content: (DocumentProxy) -> Content

    init(@ViewBuilder content: @escaping (DocumentProxy) -> Content) {
        self.content = content
    }

    var body: some View {
        content(DocumentProxy(undoManager: _undoManager))
    }
}

struct ContentView: View {

    @ObservedObject
    var document: MyDocument

    @Environment(\.newDocument) private var newDocument

    var body: some View {
        DocumentReader { proxy in
            VStack {
                Text(String("\(document.number)"))

                Button("randomize") {
                    let currentNumber = document.number
                    proxy.registerUndo(withTarget: document) { document in
                        document.number = currentNumber
                    }
                    document.number = Int.random(in: 0 ... 100)
                }

                Button("randomize without undo") {
                    document.number = Int.random(in: 0 ... 100)
                    proxy.updateDocument()
                }

                Button("New document") {
                    newDocument(MyDocument(number: 99))
                }

            }
        }
    }
}

2      

I haven't had my coffee yet, but it looks like you're only missing the associatedType requirement.

    public typealias Snapshot = ???

3      

Right, that was missing, thanks. But unfortunately it does not make a difference regarding the error message.

Did you manage to open a new ReferenceDocument programmatically in some of your SwiftUI code?

To double check I downloaded Apple's sample code from https://developer.apple.com/documentation/swiftui/building_a_document-based_app_with_swiftui and just added this to CheckListView:

    var newButton: some View {
        Button(action: {
            newDocument(ChecklistDocument())
        }) {
            Image(systemName: "star")
        }
        .buttonStyle(BorderlessButtonStyle())
    }

and I get the same error message from the compiler.

2      

Just heard back from Apple. Here is their reply which solves the problem:

The behaviour is correct. You need to change the following line of code to make it work:

newDocument.callAsFunction { MyDocument(number: 99) }

In addition to that callAsFunction as defined for ReferenceFileDocument does not accept @autoclosure. For this reason the closure needs to be defined explicitly.

2      

Hacking with Swift is sponsored by Blaze.

SPONSORED Still waiting on your CI build? Speed it up ~3x with Blaze - change one line, pay less, keep your existing GitHub workflows. First 25 HWS readers to use code HACKING at checkout get 50% off the first year. Try it now for free!

Reserve your spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.