Designing a Document-Based Application
UIDocument class, introduced in iOS 5.0, plays the principal role in document-based applications for iOS. It is an abstract base class—meaning that for you to have something useful, you must create a subclass of it that is tailored to the needs of your application. The application-specific code that you add to the subclass augments what
UIDocument makes possible: efficient behavior of documents in a mobile environment integrated with iCloud storage.
Why Create a Document-Based Application?
When you have an idea for any application and sit down to design it, you must evaluate a multitude of options. What style of application should you use (master-detail, page-based utility, OpenGL game, and so on)? Will the application do its own drawing, will it respond to gestures or touch events, and will it incorporate audiovisual assets? What will the data model be—for example, should the application use Core Data? The decision whether to make your application document-based might seem to complicate that series of choices, but it really boils down to how you anticipate people will use your application.
Document-based applications are ideal, even necessary, when users expect to enter and edit content in a visual container and store that content under a name they specify. Each container of information—a document—is unique. You are no doubt familiar with several types of document-based applications found in both desktop and mobile systems. To name a few, there are word processors, spreadsheets, and drawing programs. With the iCloud technology, you have an even more compelling reason to make your application document-based. Your users, for example, could create a document using your application on a OS X desktop system and then later, without having to do any syncing or copying, edit that document on an iPad using the iOS version of your application. This feature gives them an additional incentive to buy your application.
When you adopt the
UIDocument approach to document-based applications, your application gets a lot of behavior “for free” or with minimal coding effort on your part.
Integration with iCloud storage. The
UIDocumentobject coordinates all reading and writing of document data from and to iCloud storage. It does this by adopting the
NSFilePresenterprotocol and by calling methods of the
Background writing and reading of document data. If your application reads and writes a document synchronously, it can become momentarily unresponsive. The
UIDocumentobject avoids this problem by reading and writing document data asynchronously on a background dispatch queue.
Saveless model. In the saveless model, a user of a document-based application rarely has to save documents explicitly; The
UIDocumentobject saves document data automatically at intervals optimized for what the user is doing with the document. You make your document-based application “saveless” by implementing undo management or change tracking (see Change Tracking and Undo Operations for information).
Safe saving. The
UIDocumentobject saves document data safely; consequently, if some external event interrupts a save operation, document data won’t be left in an inconsistent state. The object implements safe saving by first writing the newest version of a document to a temporary file and then replacing the current document file with it.
Support for handling errors and version conflicts. When the
UIDocumentobject detects a conflict between different versions of a document, it notifies the application. The application can then attempt to resolve the conflict itself, or it can ask the user to pick the desired document version. The
UIDocumentobject also notifies an application when a save operation does not succeed. Managing the Life Cycle of a Document describes how to observe these notifications; Resolving Document Version Conflicts discusses strategies for dealing with document-version conflicts.
A Document in iOS
Although the broad definition of a document is a “container of information,” that container can be viewed in several ways. To a user, a document is the text, images, shapes, and other forms of information that they create, edit, and save under a unique name. A document can also refer to a document file: the persistent, on-disk representation of document data. And a document can mean a
UIDocument object that represents and manages the in-memory representation of that same data.
A document object also plays a key role in converting document data between its representation on disk to its representation in memory. It cooperates with the
UIDocument class in the writing of document data to a file and the reading of that data. For writing, a document typically provides a snapshot of data that can be written to the document file; for reading, it receives data and initializes the document’s model objects with it. A document, in a sense, is a conduit between data stored in a file and the internal representation of that data. Figure 1-1 illustrates these relationships.
A Document in iCloud Storage
In iCloud, files reside locally in a container directory that is associated with an application. That directory has an internal structure that includes, most importantly, a
Documents subdirectory. In the iCloud scheme, files and file packages written to the
Documents subdirectory are considered document files—even if they don’t originate from document-based applications. Files that are written to other locations in the container directory are considered data files.
Document objects are file presenters because the
UIDocument class adopts the
NSFilePresenter protocol. A file presenter is used together with
NSFileCoordinator objects to coordinate access to a file or directory among the objects of an application and between an application and other processes. A file presenter is involved in other clients’ access of the same presented file or directory.
When users ask to store document files in iCloud storage, a document-based application should save those files (including file packages) in the
Documents subdirectory of the iCloud container directory. See Managing the Life Cycle of a Document for specifics. For more information about iCloud mobile containers, see iCloud Design Guide.
Properties of a UIDocument Object
A document object has several defining properties, most of which relate to its role as a manager of document data. These properties are declared by the
File URL. A document must have a location where it can be stored, whether that location is in the local file system or in iCloud storage. The
fileURLproperty identifies this location. When you create a
UIDocumentobject, you must specify a file URL as the parameter of the
initWithFileURL:initializer method of
Document name. A
UIDocumentobject obtains a default document name from the filename component of the file URL and stores it in the
localizedNameproperty. You can override the getter method of this property to provide a custom, localized document name.
File type. The file type is a Uniform Type Identifier (UTI) derived from the extension of the file URL and assigned to the
fileTypeproperty. For more information, see How iOS Identifies Your Application’s Documents.
Modification date. The date the document file was last modified. This value is stored in the
fileModificationDateproperty. It can be use for (among other things) resolving document-version conflicts.
Document state. A document is in one of several possible states during its runtime life. These states can indicate, for example, that there was an error in saving the document or that there are conflicting document versions.
UIDocumentstores the current document state in the
documentStateproperty. For information about observing changes in document state, see Monitoring Document-State Changes and Handling Errors.
Design Considerations for Document-Based Applications
Before you write a line of code for your document-based application, it’s worth your while to think about a few design issues.
Defining Object Relationships
As with all applications, you should devise an overall design for the objects of your document-based application that is based on the Model-View-Controller design pattern (MVC). Although the following MVC design for documents is recommended, you are free to come up with your own object-relationship designs.
In MVC terms, a document is a model controller; it “owns” and manages the model objects that represent the document’s content. The document object itself is owned and managed by a view controller. The view controller, by definition, also manages a view that presents the content managed by the document object. It is thus a mediating controller in this network of relationships, obtaining the data it needs to present in the view from the document object and passing data entered or changed by users to the document object. Figure 1-2 depicts these object relationships.
Just as a view controller embeds its view, a view controller might embed the document object as a declared property. When your application instantiates the view controller, it initializes it with the document object or with the document’s file URL (from which the view controller itself can create the document object).
Of course, a document-based application will have other view controllers (with their views) and possibly other model objects. To get an idea of what other view controllers might be required, see Designing the User Interface.
Designing the User Interface
Neither UIKit nor the developer tools provide any support for the user interface of a document-based application. For instance, there is no
UIDocumentView class or
UIDocumentViewController class and there is no standard interface for selecting documents. Rather than regret the absence, take it as an opportunity to create a user interface for your document-based application that makes it stand out from the competition.
Nonetheless, all document-based applications should enable their users to do certain things that require user interface elements; these include the following:
Viewing and editing a document
Creating a new document
Selecting a document from a list of documents owned by the application
Opening, closing, and deleting a selected document
Putting a selected document in iCloud storage (and removing a selected document from iCloud storage)
Indicating error conditions, including document-version conflicts
Undoing and redoing changes (recommended but not required)
When you design your application, be sure to include the view controllers, views, and controls that are necessary to implement these actions.
Choosing Types, Formats, and Strategies for Document Data
When designing the data model for your document-based application, it’s critical that you ask and answer the following questions:
What is the type of my document?
A document must have a document type, a collection of information that characterizes the document and associates it with an application. A document type has a name, an icon (optional), a handler rank, and a Uniform Type Identifier (UTI) that is paired with one or more filename extensions. For an application to edit the same document in iOS and in OS X, the document-type information should be consistent.
Creating and Configuring the Project explains how to specify document types in a document-based iOS application. See Uniform Type Identifiers Overview for a description of UTIs and Uniform Type Identifiers Reference for descriptions of common Uniform Type Identifiers.
How should I represent the document data that is written to a file?
You have basically three choices:
Core Data (database). Core Data is a technology and framework for object-graph management and persistence. It can use the built-in SQLite data library as a database system. The
UIManagedDocumentclass, a subclass of
UIDocument, is intended for document-based applications that use Core Data.
Supported object formats.
NSFileWrapperobjects as native types for document-data representation.
NSDatais intended for flat files, and
NSFileWrapperis intended for file packages, which are directories that iOS treats as a single file. If you give
UIDocumentone of these objects, it saves it to the file system without further involvement on your part.
Custom object formats. If you want the document data written to a file to be of a type other than
NSFileWrapper, you can write it to the file yourself in an override of a specific
UIDocumentmethod. Keep in mind that your code will have to duplicate what
UIDocumentdoes for you, and so you must deal with greater complexity and a greater possibility of error. See UIDocument Class Reference for further information.
Why would I want to store my document data in a database instead of a file?
If the data set managed by your document object is primarily a large object graph but your application uses only a subsection of that graph at any time, then
UIManagedDocument and Core Data are a good option. Core Data brings many benefits to a document-based application:
Incremental reading and writing of document data
Support for undo and redo operations
Automatic support for resolving document-version conflicts
Data compatibility for cross-platform applications
The “downside” to Core Data is that it is a complex technology; it takes time to become familiar with its concepts, classes, and techniques. To learn more, read Core Data Programming Guide and UIManagedDocument Class Reference.
Which of the supported object formats (NSData or NSFileWrapper) is best for my application?
Whether you use an
NSFileWrapper object for document data depends on how complex that data is and on whether part of your document’s model-object graph can be written out separately to a file. For example, if your document data is plain text and nothing else, then
NSData is a suitable container for it. If, however, the data has multiple components—for example, text and images—use
NSFileWrapper methods to create a file package for the data.
File wrapper objects—and file packages which they represent—offer some advantages over binary data objects.
File wrappers support incremental saving. In contrast with a single binary data object, if a file wrapper contains your document data—for example, text and an image—and the text changes, only the file containing the text has to be written out to disk. This capability results in better performance.
File wrappers (and file packages) make it easier to version documents. A file package, for example, can contain a property list file that holds version information and other metadata about a document.
Storing Document Data in a File Package discusses (and illustrates) how you use an
NSFileWrapper object to represent document data in a form that can be written out as a file package.
Can I archive my model-object graph and have that NSData object written to the document file?
Yes, that is possible, but some of the caveats previously mentioned apply. If any part of your document’s model-object graph can be written to a separate file, don’t archive that part. Instead, store the
NSData archive and the partitioned-out items as separate components of a file package.
What if my document file is very large?
If the data stored in a document file can be large, you might have to write and read the data incrementally to ensure a good user experience. There are a few approaches you might take:
UIManagedDocument. Remember, Core Data gives you incremental reading and writing “for free.”
Store the separable components of your document data in a file package.
Override lower-level methods of
UIDocumentand do the incremental reading and writing of data yourself. (See UIDocument Class Reference for particulars.) You should always use an API for incremental reading and writing that is appropriate to the data type; for example, the AV Foundation framework has methods for incremental reading of large multimedia files.
What should I be thinking about if I want my documents to be editable on both platforms?
Your application will have a competitive advantage if your users can create and edit documents both on iOS mobile devices (iPhone, iPad, and iPad touch) and on OS X desktop systems. But for this to be possible, the format of the document data on both platforms should be compatible. Some important considerations for document-data compatibility are:
Some technologies are available on one platform but not the other. For example, if you’re using RTF as a document format in OS X, that format won’t work in iOS because its text system doesn’t support rich text.
The corresponding classes in each platform are not compatible. This is especially true with colors (
NSColor), images (
NSImage), and Bezier paths (
NSColorobjects, for example, are defined in terms of a color space (
NSColorSpace), but there is no color space class in UIKit.
If you define a document property with one of these classes, you must devise a way (when preparing the document data for writing) to convert the property into a form that can be accurately reconstituted on the other platform. One way to do this is to “drop down” to a lower-level framework that is shared by both platforms. For example,
CIColorproperty holding a Core Image object representing the color; on the OS X side, your application can create an
NSColorobject from the
CIColorobject using the
The default coordinate system for each platform is different, and this difference can affect how content is drawn. (See Coordinate Systems and Drawing in iOS in Drawing and Printing Guide for iOS for a discussion of this topic).
If you archive a document’s model-object graph, partially or entirely, then you might have to perform platform-sensitive conversions using
NSCodermethods when you encode and decode the model objects.