<!--
{
  "availability" : [
    "macOS: 27.0.0 -",
    "Xcode: 27.0.0 -",
    "iOS: 27.0.0 -"
  ],
  "documentType" : "article",
  "framework" : "SwiftUI",
  "identifier" : "/documentation/SwiftUI/Building-a-document-based-app-with-SwiftUI",
  "metadataVersion" : "0.1.0",
  "role" : "sampleCode",
  "title" : "Building a document-based app with SwiftUI"
}
-->

# Building a document-based app with SwiftUI

Create, save, and open documents in a multiplatform app.

## Overview

With this sample app, people can create, save, and open checklist documents on iPhone, iPad, Mac, and Vision Pro. In the app, people can also:

- Add, delete, and reorder checklist items.
- Select and deselect items to mark them complete.
- Undo and redo their changes.

The app uses SwiftUI’s [`DocumentGroup`](/documentation/SwiftUI/DocumentGroup) scene and [`Document`](/documentation/SwiftUI/Document) protocol to open, save, and manage checklist files, and registers its own custom document type so the system knows to open checklist files with this app.

![A screenshot displaying the document launch experience on iPad with a robot and plant accessory to the left and right of the title view, respectively.](images/com.apple.SwiftUI/writing-app-ipad@2x.png)

> Note: This sample targets the ``doc://com.apple.SwiftUI/documentation/SwiftUI/Document`` protocol described in <doc://com.apple.SwiftUI/documentation/SwiftUI/Creating-a-document-based-app> and <doc://com.apple.SwiftUI/documentation/SwiftUI/Updating-your-document-based-app>.

## Configure the sample code project

To build and run this sample on your device, select your development team for the project’s target using these steps:

1. Open the sample with the latest version of Xcode.
2. Select the top-level project.
3. For the project’s target, choose your team from the Team pop-up menu in the Signing & Capabilities pane to let Xcode automatically manage your provisioning profile.

## Create the data model

This sample has a data model that defines a checklist as a collection of items. Each item has a title and a Boolean value that tracks whether someone checked it off. `ChecklistItem` and `Checklist` conform to <doc://com.apple.documentation/documentation/Swift/Codable> for serialization, and to <doc://com.apple.documentation/documentation/Swift/Identifiable> for unique identification during enumeration. `ChecklistItem` also conforms to <doc://com.apple.documentation/documentation/Swift/Equatable> so SwiftUI can detect when an item’s content changes, as shown here:

```
struct ChecklistItem: Identifiable, Codable, Equatable {
    var id = UUID()
    var isChecked = false
    var title: String
}

struct Checklist: Identifiable, Codable {
    var id = UUID()
    var items: [ChecklistItem]
}
```

## Define the app’s scene

An app becomes document-based when its first scene in the `App` declaration is either a `DocumentGroup` or a `DocumentGroupLaunchScene`. In this sample, the document type conforms to the [`Document`](/documentation/SwiftUI/Document) protocol. The `editor` closure of the initializer returns a view that renders the document’s contents, and the `makeDocument` closure creates a new document instance, like this:

```
@main
struct DocumentBasedApp: App {
    var body: some Scene {
        DocumentGroup { document in
            ChecklistView(document: document)
        } makeDocument: { configuration, context in
            ChecklistDocument()
        }
    }
}
```

## Customize the iOS and iPadOS launch experience

You can update the default launch experience on iOS and iPadOS with a custom title, action buttons, and a screen background. To add an action button with a custom label, use `Button`. For a button that creates new documents, use a [`NewDocumentButton`](/documentation/SwiftUI/NewDocumentButton) with a custom title. You can customize the background, such as adding a view or a `backgroundStyle` with an initializer, for example, [`init(_:backgroundStyle:_:backgroundAccessoryView:overlayAccessoryView:)`](/documentation/SwiftUI/DocumentGroupLaunchScene/init(_:backgroundStyle:_:backgroundAccessoryView:overlayAccessoryView:)-2d13c). This sample customizes the background of the title view using a [`init(_:_:background:overlayAccessoryView:)`](/documentation/SwiftUI/DocumentGroupLaunchScene/init(_:_:background:overlayAccessoryView:)) initializer of [`DocumentGroupLaunchScene`](/documentation/SwiftUI/DocumentGroupLaunchScene), and places a robot and a plant on either side of the title as an overlay accessory view, as shown here:

```
DocumentGroupLaunchScene("Checklist") {
    NewDocumentButton("Start a Checklist")
} background: {
    Image(.pinkJungle)
        .resizable()
        .scaledToFill()
} overlayAccessoryView: { _ in
    AccessoryView()
}
```

Because [`DocumentGroupLaunchScene`](/documentation/SwiftUI/DocumentGroupLaunchScene) isn’t available in macOS, add this scene alongside the sample’s [`DocumentGroup`](/documentation/SwiftUI/DocumentGroup) scene within an `#if os(iOS)` conditional compilation block.

## Adopt the document protocol

The `ChecklistDocument` class adopts the [`Document`](/documentation/SwiftUI/Document) protocol to read and write checklists from and to files. Because [`Document`](/documentation/SwiftUI/Document) requires a reference type, `ChecklistDocument` is a `final class` marked with <doc://com.apple.documentation/documentation/Observation/Observable()>, rather than a structure. The [`readableContentTypes`](/documentation/SwiftUI/ReadableDocument/readableContentTypes) property defines the types that the sample can read, specifically, the `.checklistDocument` type, like this:

```
static let readableContentTypes: [UTType] = [.checklistDocument]
```

The sample reads a checklist from a file using a [`DocumentReader`](/documentation/SwiftUI/DocumentReader) that its [`reader(configuration:)`](/documentation/SwiftUI/ReadableDocument/reader(configuration:)) method returns. This sample uses [`FileWrapperDocumentReader`](/documentation/SwiftUI/FileWrapperDocumentReader) with a closure that decodes a file wrapper’s contents using a <doc://com.apple.documentation/documentation/Foundation/JSONDecoder>, as shown here:

```
func reader(configuration: sending ReadConfiguration) -> sending FileWrapperDocumentReader<Checklist> {
    FileWrapperDocumentReader(configuration) { fileWrapper in
        guard let data = fileWrapper.regularFileContents else {
            throw CocoaError(.fileReadCorruptFile)
        }
        return try JSONDecoder().decode(Checklist.self, from: data)
    }
}
```

After SwiftUI reads a checklist in the background, it delivers the result to the document’s [`apply(snapshot:previous:)`](/documentation/SwiftUI/ReadableDocument/apply(snapshot:previous:)) method on the main actor, which updates the document’s observable state, like this:

```
@MainActor
func apply(snapshot: sending Checklist, previous: sending Checklist?) async throws {
    checklist = snapshot
}
```

When someone saves the document, the sample returns a snapshot of its data from [`snapshot(contentType:)`](/documentation/SwiftUI/WritableDocument/snapshot(contentType:)), which also runs on the main actor as follows:

```
@MainActor
func snapshot(contentType: UTType) async throws -> sending Checklist {
    checklist // Make a copy.
}
```

Conversely, the [`writer(configuration:)`](/documentation/SwiftUI/WritableDocument/writer(configuration:)) method returns a [`DocumentWriter`](/documentation/SwiftUI/DocumentWriter) that encodes the snapshot and writes it to disk. This sample uses [`FileWrapperDocumentWriter`](/documentation/SwiftUI/FileWrapperDocumentWriter) with a closure that serializes the snapshot into a file wrapper using a <doc://com.apple.documentation/documentation/Foundation/JSONEncoder> instance, like this:

```
func writer(configuration: sending WriteConfiguration) -> sending FileWrapperDocumentWriter<Checklist> {
    FileWrapperDocumentWriter(configuration) { snapshot, _ in
        let data = try JSONEncoder().encode(snapshot)
        return FileWrapper(regularFileWithContents: data)
    }
}
```

## Register undo and redo actions

With the [`Document`](/documentation/SwiftUI/Document) protocol, undo management is mandatory to enable autosave. Read the active <doc://com.apple.documentation/documentation/Foundation/UndoManager> from the environment and update the document through methods that register an undo action. Calling the same method again from the undo closure also registers the redo action, so most operations only need one method, as shown here:

```
@MainActor
func toggleItem(_ item: Binding<ChecklistItem>, undoManager: UndoManager? = nil) {
    item.wrappedValue.isChecked.toggle()

    undoManager?.registerUndo(withTarget: self) { doc in
        doc.toggleItem(item, undoManager: undoManager)
    }
}
```

## Export a custom document type

The app defines and exports a custom content type for the documents it creates. It declares this custom type in the project’s <doc://com.apple.documentation/documentation/BundleResources/Information-Property-List> file under the <doc://com.apple.documentation/documentation/BundleResources/Information-Property-List/UTExportedTypeDeclarations> key. This sample uses `com.example.checklist` as the identifier in the information property list file, as the following code demonstrates:

```
<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>LSHandlerRank</key>
        <string>Default</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>com.example.checklist</string>
        </array>
        <key>NSUbiquitousDocumentUserActivityType</key>
        <string>$(PRODUCT_BUNDLE_IDENTIFIER).example-document</string>
    </dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
    <dict>
        <key>UTTypeConformsTo</key>
        <array>
            <string>public.data</string>
            <string>public.content</string>
        </array>
        <key>UTTypeDescription</key>
        <string>Checklist Document</string>
        <key>UTTypeIconFiles</key>
        <array/>
        <key>UTTypeIdentifier</key>
        <string>com.example.checklist</string>
        <key>UTTypeTagSpecification</key>
        <dict>
            <key>public.filename-extension</key>
            <array>
                <string>checklist</string>
            </array>
        </dict>
    </dict>
</array>
```

For convenience, you can also define the content type in code, as seen in the following example:

```
extension UTType {
    static let checklistDocument = UTType(exportedAs: "com.example.checklist")
}
```

Specify a file extension for every custom format you declare to make sure the operating system opens files with the given extension using your app. For more information about custom file and data types, see <doc://com.apple.documentation/documentation/UniformTypeIdentifiers/defining-file-and-data-types-for-your-app>.

## See Also

#### Related samples

[Building a document-based app using SwiftData](/documentation/SwiftUI/Building-a-document-based-app-using-SwiftData)

Code along with the WWDC presenter to transform an app with SwiftData.

#### Related articles

[Creating a document-based app](/documentation/SwiftUI/Creating-a-document-based-app)

Build apps that people can use to open, edit, and save files using coordinated file access.

[Updating your document-based app](/documentation/SwiftUI/Updating-your-document-based-app)

Migrate an existing app to adopt URL-based document reading and writing with Swift concurrency.

  <doc://com.apple.documentation/documentation/UniformTypeIdentifiers/defining-file-and-data-types-for-your-app>

#### Related videos

  <doc://com.apple.documentation/videos/play/wwdc2026/269>



---

Copyright &copy; 2026 Apple Inc. All rights reserved. | [Terms of Use](https://www.apple.com/legal/internet-services/terms/site.html) | [Privacy Policy](https://www.apple.com/privacy/privacy-policy)