Build document-based apps in SwiftUI

Written by Kuba Suder

Description: Learn how to build a document-based app entirely in SwiftUI! We’ll walk you through the DocumentGroup API and how it composes with your App and Scenes, allowing you to add out-of-the-box support for document management — such as document browsing and standard commands — no heavy lifting required. You’ll learn to set up Universal Type Identifiers as well as gain understanding into what makes a top-notch document-based app. To get the most out of this session, you should first familiarize yourself with building apps in SwiftUI. Check out "App essentials in SwiftUI" to learn more.

For document based apps, use the DocumentGroup scene as the main scene of the app:

DocumentGroup(newDocument: MyDocument()) { file in
  ContentView(document: file.$document)

The content view receives a binding to the document contents, so when it changes the contents, the system knows it was modified

Document based apps have a “Document Types” section in the Info.plist, where you declare Uniform Type Identifiers of document types associated with your app:

  • Imported types ⭢ documents from other apps or sources that your app is able to open
  • Exported types ⭢ document types owned by your app

TextEditor() - built in text view type

The type that implements the document model conforms to FileDocument (for value types) and declares its own UTType instances that represent imported and exported file types:

import UniformTypeIdentifiers

extension UTType {
    static var exampleText: UTType {
        UTType(importedAs: "com.example.plain-text")

    static let shapeEditDocument =
        UTType(exportedAs: "com.example.ShapeEdit.shapes")

Imported type needs to be a computed property, because the value returned from the constructor may change between calls while the app is running, depending on system configuration. Exported type can just be assigned once and stored.

The document type provides a list of types (own and generic) that it can accept:

static var readableContentTypes: [UTType] { [.exampleText] }

It also has methods for reading and writing its document to/from a file, which you need to implement, using e.g. Codable to encode/decode the value into your chosen format:

init(fileWrapper: FileWrapper, contentType: UTType) throws { ... }

func write(to fileWrapper: inout FileWrapper, contentType: UTType) throws { ... }

In those methods, you can assume that the content type is one of those you declared as accepted by your app.

This note was originally published at

Missing anything? Corrections? Contributions are welcome 😃


Written by

Kuba Suder

Kuba Suder

Kuba Suder is an independent Mac/iOS/watchOS developer from Kraków, Poland. Currently busy working on his own apps and projects, learning more about Apple platforms and sharing the knowledge on his blog. Language of choice: Swift for apps, Ruby on the backend.