Sample Code

Building a Document-Based App with SwiftUI

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

Overview 概要

This sample creates a checklist document that can have one or more checklist items. The user can select and deselect the checkboxes of items in the list, add and delete items, and rearrange items. The sample implements a DocumentGroup scene, and adopts the ReferenceFileDocument protocol.

Configure the Sample Code Project 見本のコードプロジェクトを構成設定する

To build and run this sample on your device, you must first 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 drop-down menu in the Signing & Capabilities pane to let Xcode automatically manage your provisioning profile.

Create the Data Model

This sample is a simple checklist app that keeps track of one or more items in a checklist, and whether the checkboxes of the items are in a selected state. The app has a data model that defines ChecklistItem and Checklist objects, and these objects conform to Codable to enable easy serialization. They also conform to Identifiable for unique identification during enumeration.


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


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

Export the App’s Document Type

The app defines and exports a custom checklist document type that tells the operating system to open checklist documents with this app. The app does this by including an entry in its Information Property List file under the CFBundleDocumentTypes and UTExportedTypeDeclarations keys. Additionally, the sample defines the bundle’s exported type as a UTType to support the sample app’s data format.


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

For more information, see Defining File and Data Types for Your App.

Define the App’s Scene

A document-based SwiftUI app returns a DocumentGroup scene from its body property. The newDocument parameter that an app supplies to the document group’s init(newDocument:editor:) initializer must conform to FileDocument or ReferenceFileDocument. This sample adopts ReferenceFileDocument. The trailing closure of the initializer returns a view that renders the document’s contents.


@main
struct DocumentBasedApp: App {
    var body: some Scene {
        DocumentGroup(newDocument: { ChecklistDocument() }) { configuration in
            ChecklistView()
        }
    }
}

Adopt the Reference File Document Protocol

The sample’s ChecklistDocument structure adopts the ReferenceFileDocument protocol to serialize checklists to and from files. This sample implements the required properties and methods to conform to the protocol. The readableContentTypes property defines the types that the sample can read, namely, the .checklistDocument type.


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

The sample reads data from a document in the init(configuration:) method. After reading the file’s data, the initializer uses a JSONDecoder to deserialize the data into model objects.


init(configuration: ReadConfiguration) throws {
    guard let data = configuration.file.regularFileContents
    else {
        throw CocoaError(.fileReadCorruptFile)
    }
    self.checklist = try JSONDecoder().decode(Checklist.self, from: data)
}

When the user saves the document, the sample returns a snapshot of the document’s data for serialization in the snapshot(contentType:) instance method.


func snapshot(contentType: UTType) throws -> Checklist {
    checklist // Make a copy.
}

Conversely, in the fileWrapper(configuration:) method, a JSONEncoder instance serializes the data model into a FileWrapper object that represents the data in the file system.


func fileWrapper(snapshot: Checklist, configuration: WriteConfiguration) throws -> FileWrapper {
    let data = try JSONEncoder().encode(snapshot)
    let fileWrapper = FileWrapper(regularFileWithContents: data)
    return fileWrapper
}

Register Undo and Redo Actions

In an app that uses FileDocument for its document object, undo management and the registration of undo actions are automatic. However, because this sample uses a ReferenceFileDocument document class, the sample itself must perform undo management. Implementing undo management also alerts SwiftUI when the document changes. The UndoManager class handles undo management, and SwiftUI supplies an instance of this class in the environment.


@Environment(\.undoManager) var undoManager

In this sample, the SwiftUI views handle user actions by calling the ChecklistDocument and passing along the UndoManager object.


document.toggleItem($item.wrappedValue, undoManager: undoManager)

The ChecklistDocument toggles the isChecked state of the ChecklistItem, and registers an undo action that calls itself. This way, the sample doesn’t need to register a redo action when performing an undo action.


func toggleItem(_ item: ChecklistItem, undoManager: UndoManager? = nil) {
    let index = checklist.items.firstIndex(of: item)!
    
    checklist.items[index].isChecked.toggle()
    
    undoManager?.registerUndo(withTarget: self) { doc in
        // Because it calls itself, this is redoable, as well.
        doc.toggleItem(item, undoManager: undoManager)
    }
}

See Also 参照

Essentials 要点