How it works
ReferenceFileDocument is the protocol you adopt to back a document-based app with a reference type, typically an ObservableObject class whose properties drive your views directly. Use it instead of the value-type FileDocument when your model is naturally a class, when you need the document instance to be observed and mutated in place, or when saving large files should not block editing. Because saving happens on a separate snapshot rather than the live object, SwiftUI can serialize a document while the user keeps working. Reach for it whenever your editable content lives in a shared, mutable object that several views read and write.
Adopt the protocol on a reference type
Conform a class to
ReferenceFileDocumentso the same instance can be shared and observed across your UI. HereTextDocumentis afinal classwhose@Published var textis the editable state; marking it published lets SwiftUI re-render whenever the document's contents change.Declare the formats with readableContentTypes
The static
readableContentTypesrequirement lists theUTTypevalues your document can open, telling the system which files to offer and how to tag them.TextDocumentreturns[.plainText], declaring it reads and writes plain-text files (theUTTypeAPI comes fromimport UniformTypeIdentifiers).Read existing files with init(configuration:)
The throwing
init(configuration:)builds your document from a file the user opens, receiving aReadConfigurationwhosefile.regularFileContentsholds the bytes.TextDocumentdecodes thatDataas UTF-8 intotext, falling back to an empty string when there are no contents.Capture a snapshot before writing
Unlike
FileDocument, saving is split in two.snapshot(contentType:)is called first, on the main actor, to grab an immutable copy of whatever the document needs to persist;TextDocumentsimply returns its currenttext. SwiftUI then hands that snapshot to the writer so editing can continue uninterrupted while the save proceeds.Serialize the snapshot with fileWrapper(snapshot:configuration:)
fileWrapper(snapshot:configuration:)runs off the main actor and turns the captured snapshot into aFileWrapperfor disk, guided by aWriteConfiguration.TextDocumentwrapsData(snapshot.utf8)in aFileWrapper(regularFileWithContents:), and because it works from the snapshot rather than the live object the write is safe to perform in the background.
snapshot(contentType:) body from text to text.uppercased() and observe that saved files are upper-cased while the live $document.text in the TextEditor stays exactly as typed.Example & preview
Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.
import UniformTypeIdentifiers
final class TextDocument: ReferenceFileDocument {
static var readableContentTypes: [UTType] { [.plainText] }
@Published var text: String
init(text: String = "Hello, document!") {
self.text = text
}
init(configuration: ReadConfiguration) throws {
if let data = configuration.file.regularFileContents {
text = String(decoding: data, as: UTF8.self)
} else {
text = ""
}
}
func snapshot(contentType: UTType) throws -> String { text }
func fileWrapper(snapshot: String, configuration: WriteConfiguration) throws -> FileWrapper {
FileWrapper(regularFileWithContents: Data(snapshot.utf8))
}
}
struct ReferenceFileDocumentDemo: View {
@StateObject private var document = TextDocument()
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("ReferenceFileDocument")
.font(.headline)
TextEditor(text: $document.text)
.frame(height: 120)
.border(.secondary)
Text("Edited as a reference document")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding()
}
}