How it works
FileDocument is the protocol you adopt to make a value type that SwiftUI can read from and write to a file on the user's behalf. It bridges your in-memory model to a document on disk, letting a DocumentGroup scene handle opening, saving, autosave, and reverting without you managing file URLs or coordination directly. Reach for it when your document state is a value type that can be snapshotted cheaply, since SwiftUI serializes a copy on a background queue while editing continues. The conforming type declares which content types it understands, decodes itself from a file, and produces a file wrapper when it's time to write.
Declare the supported content types with readableContentTypes
FileDocument requires a static readableContentTypes property returning the UTType values your document can open. SwiftUI uses it to filter the open panel and to tag files your document creates. Here NoteDoc reports
[.plainText], so it advertises itself as a plain-text editor.Decode from disk in init(configuration:)
The protocol's required initializer,
init(configuration: ReadConfiguration) throws, hands you the file's contents so you can build your model. You read the bytes throughconfiguration.file.regularFileContentsand reconstruct state from them. NoteDoc decodes thatdataas UTF-8 intotext, falling back to an empty string when there are no contents to read.Serialize with fileWrapper(configuration:)
When SwiftUI needs to save, it calls
fileWrapper(configuration:), which must return a FileWrapper describing the bytes to write. Because FileDocument is a value type, this runs on a snapshot of your document, so editing stays responsive. NoteDoc wraps itstextas UTF-8 withFileWrapper(regularFileWithContents:).Keep the document as editable value-type state
A FileDocument is meant to be held as state and bound into your views, so edits flow straight into the model that will be saved. NoteDoc is stored in
@State private var docand itstextis bound into aTextEditor(text: $doc.text), so each keystroke mutates the same value SwiftUI will later serialize.
.json to the array in readableContentTypes and watch the document advertise an additional content type that the open and save panels will accept.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.
struct FileDocumentDemo: View {
struct NoteDoc: FileDocument {
static var readableContentTypes: [UTType] { [.plainText] }
var text: String
init(text: String = "Hello") { self.text = text }
init(configuration: ReadConfiguration) throws {
if let data = configuration.file.regularFileContents,
let s = String(data: data, encoding: .utf8) {
text = s
} else { text = "" }
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
FileWrapper(regularFileWithContents: Data(text.utf8))
}
}
@State private var doc = NoteDoc(text: "Draft note")
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("FileDocument").font(.headline)
Text("Conforming type: NoteDoc")
.font(.caption).foregroundStyle(.secondary)
TextEditor(text: $doc.text)
.frame(height: 100)
.border(.quaternary)
}
.padding()
}
}