TechnologiesSwiftUI

FileDocument protocol

iOSmacOStvOSwatchOSvisionOS✓ renders

A type that you use to serialize documents to and from file.

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.

  1. 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.

  2. 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 through configuration.file.regularFileContents and reconstruct state from them. NoteDoc decodes that data as UTF-8 into text, falling back to an empty string when there are no contents to read.

  3. 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 its text as UTF-8 with FileWrapper(regularFileWithContents:).

  4. 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 doc and its text is bound into a TextEditor(text: $doc.text), so each keystroke mutates the same value SwiftUI will later serialize.

Try it — Add .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.

FileDocument.swift
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()
    }
}
Live preview
FileDocument Conforming type: NoteDoc Draft note
FileDocument Conforming type: NoteDoc Draft note
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →