Contents

UIDocument

An abstract base class for managing discrete portions of your app’s data.

Declaration

nonisolated class UIDocument

Mentioned in

Overview

Apps that make use of UIDocument and its underlying architecture get many benefits for their documents:

  • Asynchronous reading and writing of data on a background queue, meaning your app’s responsiveness is unaffected while reading and writing operations take place

  • Coordinated reading and writing of document files automatically integrated with cloud services

  • Support for discovering conflicts between different versions of a document

  • Safe-saving of document data by writing data first to a temporary file and then replacing the current document file with it

  • Automatic saving of document data at opportune moments and support for dealing with suspend behaviors

In the Model-View-Controller design pattern, a UIDocument object is a model object or model-controller object — it manages the data of a document or the aggregate model objects that together constitute the document’s data. You typically pair it with a view controller that manages the view presenting the document’s contents. UIDocument provides no direct support for managing document views, but view controllers that subclass UIDocumentViewController can present a UIDocument, and view controllers that subclass UIDocumentBrowserViewController can organize and display UIDocument collections.

Document-based apps include those that can generate multiple documents, each with its own file-system location. A document-based app must create a subclass of UIDocument for its documents.

The primary attribute of a document in the UIDocument architecture is its file URL. When you initialize an instance of your document subclass by calling init(fileURL:), you must pass a file URL locating the document file in the app sandbox. UIDocument determines the file type (the Uniform Type Identifier associated with the file extension) and the document name (the filename component) from the file URL. You can override the accessor methods of the fileType and localizedName properties to supply different values.

The following outlines the life cycle of a typical document:

  1. You create a new document or open an existing document.

    • To create a new document, allocate and initialize an instance of your subclass and then call save(to:for:completionHandler:) on the instance.

    • To open an existing document (selected by the user), allocate and initialize an instance of your subclass and then call open(completionHandler:) on the instance.

  2. The user edits the document. As the user edits, track changes to the document. UIDocument periodically notes when there are unsaved changes and writes the document data to its file.

  3. The user requests that the document be integrated with cloud services (optional). You must enable the document for cloud storage. You must also resolve any conflicts between different versions of the same document.

  4. The user closes the document. Call close(completionHandler:) on the document instance. UIDocument saves the document if there are any unsaved changes.

A typical document-based app calls open(completionHandler:), close(completionHandler:), and save(to:for:completionHandler:) on the main thread. When the read or save operation kicked off by these methods concludes, the system executes the completion-handler block on the same dispatch queue as the system used to invoke the method, allowing you to complete any tasks contingent on the read or save operation. If the operation isn’t successful, the system passes false to the completion-handler block.

Implement the NSFilePresenter protocol

The UIDocument class adopts the NSFilePresenter protocol. When another client attempts to read the document of a UIDocument-based app, the system suspends reading until the system provides the UIDocument object an opportunity to save any changes made to the document.

Although some implementations do nothing, UIDocument implements all NSFilePresenter methods. Specifically, UIDocument:

In your UIDocument subclass, if you override a NSFilePresenter method, you can always invoke the superclass implementation (super).

Create a subclass

Each document-based app must create a subclass of UIDocument whose instances represent its documents. The subclassing requirements for most apps are simple:

If you have special requirements for reading and writing document data for which the contents(forType:) and load(fromContents:ofType:) methods won’t suffice, you can override other methods of the UIDocument class. See Override input and output methods for a discussion of these requirements and methods.

Track changes

To enable the autosaving feature of UIDocument, you must notify it when users make changes to a document. UIDocument periodically checks whether the hasUnsavedChanges method returns true; if it does, it initiates the save operation for the document.

There are two primary ways to implement change tracking in your UIDocument subclass:

  • Call the methods of the UndoManager class to implement undo and redo for the document. You can access the default UndoManager object from the undoManager property. This is the preferred approach, especially for existing apps that already support undo and redo.

  • Call the updateChangeCount(_:) method at the appropriate junctures in your code.

Resolve conflicts and handle errors

A UIDocument object has a specific state at any moment in its life cycle. You can check the current state by querying the documentState property, and get notified about changes by observing the stateChangedNotification notification.

If the owner enables a document for iCloud, it’s important to check for conflicting versions and to attempt to resolve conflicts. Listen for the stateChangedNotification notification and then checking if the document state is inConflict. This state indicates that there are conflicting versions of the document, which you can access by calling the NSFileVersion class method unresolvedConflictVersionsOfItem(at:), passing in the document’s file URL. If you can resolve a conflict correctly without user interaction, do so. Otherwise, discretely notify the user that a conflict exists and let them choose how to resolve it. Possible approaches include:

  • Display the conflicting versions, from which a user can pick one or both versions to keep.

  • Display a merged version and giving the user an option to pick it.

  • Display the file modification dates and giving the user the option to choose one or both.

Document state, in addition to indicating an inter-file conflict, can indicate errors. For example, closed indicates an error in reading, and savingError indicates an error in saving or reverting a document. The system notifies your app of reading and writing errors through the success parameter passed into the completion handlers of the open(completionHandler:), close(completionHandler:), revert(toContentsOf:completionHandler:), and save(to:for:completionHandler:) methods.

You can handle errors by calling or implementing the handleError(_:userInteractionPermitted:) method; the default implementations of the open(completionHandler:) and save(to:for:completionHandler:) methods call handleError(_:userInteractionPermitted:) when a UIDocument object encounters a reading or writing error, respectively. You can handle read, save, and reversion errors by informing the user and, if the situation permits, trying to recover from the error.

Be sure to read the description for the contents(forType:) method for its guidance on handling errors encountered during document saving.

Override input and output methods

If you app has special requirements for reading or writing document data, it can override methods of UIDocument other than load(fromContents:ofType:) and contents(forType:). These requirements often include the following:

If you override these methods, be aware that all reading and writing of document data must be done on a background queue and must be coordinated with other attempts to read from and write to the same document file. Because of this, you usually call the superclass implementation (super) as part of your override, and if you call other UIDocument methods, you usually invoke them in the block passed into a call of the performAsynchronousFileAccess(_:) method. Read the method descriptions for details.

Access document attributes

If you override any of the document-attribute properties (listed under Accessing document attributes) by overriding the related accessor methods, be aware that the UIKit framework can call these accessor methods on a background thread. Thus your overriding implementation must be thread safe.

Rename documents

UIDocument provides support for changing the document’s title. Security considerations require that clients can’t programmatically rename a file on the file system, and that the system confirms that a person intends to rename their file. To satisfy these restrictions, the system, instead of your app, presents a renaming user interface using a process outside your app. The external process renames the underlying file and reports the new location back to the client.

To support this external process, UIDocument conforms to UINavigationItemRenameDelegate and handles the rename request internally when a person invokes renaming from the title menu. If you’re using UIDocumentViewController, it automatically configures renaming for you. Otherwise, you manually assign the document as the navigation item’s renameDelegate.

init(document: MyDocument) {
    self.document = document
    super.init(nibName:nil, bundle: nil)
    self.navigationItem.renameDelegate = document
}

The Rename action appears in the title menu as one of the system-suggested actions. When a person taps the Rename action, the system shows an inline text field for changing the navigation item’s title. Upon renaming the item, the system changes the file name in storage as though the person renamed the file in another application.

Prior to iOS 17, to enable the system rename user interface, a client view controller adopts the UINavigationItemRenameDelegate protocol and assigns itself as the navigation item’s renameDelegate. It’s the client’s responsibility to implement callbacks such as navigationItem(_:didEndRenamingWith:) (Swift) or navigationItem:didEndRenamingWithTitle: (Objective-C) to explicitly move the file in storage.

class EditorViewController: UIViewController,
        UINavigationItemRenameDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.renameDelegate = self
    }

    func navigationItem(_ navigationItem: UINavigationItem, didEndRenamingWith: title: String) {
        // Move the file, update the model, and so on.
    }
}

Topics

Initializing a document object

Accessing document attributes

Writing document data

Reading document data

Creating new documents

Accessing document files asynchronously

Reverting a document

Disabling and enabling editing

Tracking changes and autosaving

Supporting user activities

Resolving conflicts and handling errors

Constants

Notifications

Structures

Type Properties

See Also

Documents