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