Using ReferenceFileDocument to Save SwiftUI Documents
When you create a SwiftUI Document App project, the Swift file for the document contains a struct for the document that conforms to FileDocument. Using a struct for the document works in most cases, but you can make the document a class by conforming to ReferenceFileDocument.
When to Make Your SwiftUI Document a Class
The most common reason to use a class for a SwiftUI document is to update SwiftUI views when a property in the document changes. Using the @Published property wrapper for a property triggers updates to SwiftUI views when the property’s value changes. But the @Published property wrapper works only for classes.
If you are going to pass the document to many views, it can be more effective to make the document a class and use @StateObject, @ObservedObject, or @EnvironmentObject to pass the document to other views.
If you can’t mark the document as changed when using a struct, you can mark the document as changed by making the document a class and registering with the undo manager.
Changing the Document from a Struct to a Class
Apple’s Swift file for the document creates a struct.
struct Document: FileDocument
You must make the following changes to change the document from a struct to a class:
- Change
structtoclass - Change
FileDocumenttoReferenceFileDocument - Have the document conform to
ObservableObject
class Document: ReferenceFileDocument, ObservableObject
Creating a Snapshot
You must add a snapshot function to your document’s class to use ReferenceFileDocument. The snapshot function creates a snapshot of the document’s current state that SwiftUI uses to save the document.
func snapshot(contentType: UTType) throws -> GameScene {
return self.scene
}
Replace GameScene with the data type your document saves. Replace scene with a property in your document class.
Marking the Document as Changed
If you find your document isn’t marked as changed when you make changes to the document, register your document with the undo manager by calling the registerUndo function. Usually you call registerUndo from a view’s .onAppear modifier.
// Add this property to the SwiftUI view.
@Environment(\.undoManager) var undoManager
.onAppear {
undoManager?.registerUndo(withTarget: document, handler: {
// Use the handler to do anything your app
// needs to do when registering the undo.
print($0, "undo")
})
}
Acknowledgements
The answers to the following questions on Stack Overflow helped me write this article: