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
struct
toclass
- Change
FileDocument
toReferenceFileDocument
- 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: