Document-based Apps

Overview

SwiftUI document-based apps "are apps that let users create, edit, and share documents such as text files."

The information below was derived form the Hacking With Swift article at How to create a document-based app using FileDocument and DocumentGroup.

Steps

In this example we are building a way to create Lua source files in a SwiftUI app.

The steps to create such a document-based app are:

  1. Add the .icns file to the project.

  2. Define a custom file extension for your documents. If the type is known outside of your app, add an entry in the "Imported Type Identifiers" section. If the type is unique to your app, add an entry in the "Exported Type Identifiers" section. The form fields in each section are the same. Then add an entry in the "Document Types" section.

    Xcode Custom File Type

  3. Optionally create an icon for your file type. Create an image that is 1024 x 1024. Browse cloudconvert, click the "Select File" button, select your file, select "ICNS" in the "Convert to" dropdown, and click the "Convert" button. When it completes, click the "Download" button. Within the "Icons" section, click the "+" button, select your .icns file, and click the "Add" button.

  4. In the struct that conforms to App, in the computed property body, replace the following:

    WindowGroup {
    ContentView()
    }

    with this:

    DocumentGroup(newDocument: TextFile()) { file in
    ContentView(document: file.$document)
    }
  5. Define a struct that conforms to the FileDocument protocol. This will hold all the data associated with each document using one or more properties. For example, for basic text documents we could create the file TextFile.swift containing the following:

    import SwiftUI
    import UniformTypeIdentifiers

    extension UTType {
    // If you defined an "Imported Type Identifier" use "importedAs" here.
    // If you defined an "Exported Type Identifier" use "exportedAs" here.
    static let luaSource = UTType(importedAs: "org.lua")
    }

    struct LuaFile: FileDocument {
    static let fileExtension = "lua"
    static var readableContentTypes = [UTType.luaSource]
    static let defaultCode = """
    x = 3
    y = 4
    if x >= y then
    color = "green"
    else
    color = "red"
    end
    -- color = x >= y and "red" or "green"

    greeting = "Hello!"
    """


    var code = ""

    // This creates a document containing the given code.
    init(initialCode: String = defaultCode) {
    code = initialCode
    }

    // This creates a document containing previously saved code.
    init(configuration: ReadConfiguration) throws {
    if let data = configuration.file.regularFileContents {
    code = String(decoding: data, as: UTF8.self)
    } else {
    throw CocoaError(.fileReadCorruptFile)
    }
    }

    // This is called when the system wants to save code in a file.
    // TODO: How can the file name be specified?
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
    let data = Data(code.utf8)
    return FileWrapper(regularFileWithContents: data)
    }
    }
  6. Add a view for editing the current document. This can be a custom UI that is suitable for the struct defined above. For a text document it could be as simple as a TextEditor view. For example:

    @Binding var luaFile: LuaFile

    ...

    TextEditor(text: $luaFile.text)
    .autocapitalization(.none)
    .disableAutocorrection(true)
    .lineLimit(lines)
    .frame(
    maxWidth: .infinity,
    maxHeight: CGFloat(lines * lineHeight)
    )
    .border(.gray)
  7. Change the project settings to indicate that it will use the system document browser.

    1. Select the top entry in the Project Navigator.
    2. Select the main target.
    3. Select the "Info" tab.
    4. Hover over an existing row and click the "+" button.
    5. Select "Supports Document Browser".
    6. Set the value to "YES".

Result

The app in the screenshots below can be found at SwiftUICallsC. It demonstrates embedding the Lua interpreter in a SwiftUI app. Each document contains Lua code that is executed by tapping the "Execute" button.

When users run the app they are presented with a document picker screen. There are three tabs at the bottom labelled "Recents", "Shared", and "Browse". The "Recents" tab displays thumbnail buttons for recently accessed documents. Initially there will not be any. In the screenshot below, two documents named "Complex" and "Simple" have already been created.

SwiftUI Document-Based App Recents

The "Browse" tab displays a "Create Document" button along with thumbnail buttons for all documents already created.

SwiftUI Document-Based App Browse

Clicking the "Create Document" button or the button for an existing document advances to the view passed to DocumentGroup inside the struct that conforms to App.

SwiftUI Document-Based App Document

To return to the document picker screen, tap the "<" button in the upper-left.

To rename a document, long press it, select "Rename", and replace the current name.