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 to class
  • Change FileDocument to ReferenceFileDocument
  • 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:

Get the Swift Dev Journal Newsletter

Subscribe and get exclusive articles, a free guide on moving from tutorials to making your first app, notices of sales on books, and anything I decide to add in the future.

    We won't send you spam. Unsubscribe at any time.