< Previous PageNext Page > Hide TOC

Frequently Asked Questions

This article answers commonly asked questions about the document-handling classes in the Application Kit. This includes the NSDocument class as well as NSDocumentController and NSWindowController.

Contents:

How do I start?
How do I define types?
How do I implement saving and loading for simple files?
How do I implement document packages?
How do I implement loading and saving when the simple data or file wrapper API won’t do?
Should I subclass NSWindowController?
How do I subclass NSWindowController?
When can I do setup operations on my user interface objects?
How can I support read-only types?
How can I support write-only types?
How can I support reading one type and automatically converting (internally) to another?
How can I customize the Save panel?
How do I implement printing?
Should I do anything about print info?
How can I make an NSWindowController subclass that automatically uses a particular nib file?
How can I use NSWindowController for shared panels (inspectors, find panels, etc.)?
How can I use multiple NSWindowControllers for a single document?
How can I customize the window title for a document’s NSWindowController?
How do I implement undo?
How do I implement partial undo?
What if I don't want to support undo?
What's all this change count stuff?
Should I subclass NSDocumentController?
How can I subclass NSDocumentController?
How can I create new documents other than through user-action methods?
How can I keep my application from creating an untitled document at launch?


How do I start?

To get started writing an NSDocument-based application, use the Cocoa Document-based Application project template when creating a new Xcode project. When you do this, you get a new application project that already contains a subclass of NSDocument and a document nib file.

The NSDocument subclass is pre-set to load the document nib file. Empty methods are provided for loading and saving; comments explain what you need to fill in. A method is also provided for you to add code that is called after the document nib file is loaded.

Without writing any additional code, you should be able to compile and run the application. You will see an untitled document with an empty window created when you first launch the application, and the File menu commands all do something reasonable, such as bringing up a Save panel or Open panel. Because you have not yet defined any types or implemented loading and saving, you can't actually open or save anything.

From here, you should start by defining a document type (see “How do I define types?”) and by implementing the load and save methods (see “How do I implement saving and loading for simple files?”).

Beyond defining types and implementing loading and saving, you will certainly need to implement other capabilities in your document subclass. The document subclass should contain and own the contents of the document. This means that your NSDocument subclass should provide methods for maintaining and managing the document contents (that is, the document's model objects).

For more information on writing a document-based application, see “Implementing a Document-Based Application.” For more information on subclassing NSDocument, see “Creating a Subclass of NSDocument.” For an example of an application that follows the application design suggestions, see the Sketch example in /Developer/Examples/AppKit/Sketch.

How do I define types?

Types are defined in a file called Info.plist, which is managed for you by Xcode. For details on information property lists, see “Storing Document Types Information in the Application's Property List,” as well as Property List Programming Guide.

You can define types for your application in the Target inspector in Xcode. The Target inspector provides an editable list of document types, as shown in “Storing Document Types Information in the Application's Property List.”

For a new application, you should create a type with a name and extension that make sense for your application. You can add more types as well.

The application's “main” or most important document type should be listed first in the list of types. This is the type NSDocumentController will use by default when the user asks for a new document.

How do I implement saving and loading for simple files?

A new document-based application project comes with empty method implementations for dataRepresentationOfType: and loadDataRepresentation:ofType: in the custom subclass that is automatically created for you. You should implement these methods to support reading and writing of simple files if your application must be able to run on Mac OS X v10.3 or earlier.

For applications that can require Mac OS X v10.4 and later, override dataOfType:error: and readFromData:ofType:error:, respectively, instead. For example implementations of those overrides, see “Implementing a Document-Based Application” and the Sketch example application at /Developer/Examples/AppKit/Sketch/.

The dataOfType:error: method should provide the contents of the document as an NSData object, formatted as the requested type.

The readFromData:ofType:error: method should be able to read in the document contents from the given NSData, interpreting the data as the given type.

If your document saves document as file wrappers or has other more sophisticated needs, see “How do I implement document packages?” and “How do I implement loading and saving when the simple data or file wrapper API won’t do?” for more information.

How do I implement document packages?

Document packages are really folders, but they appear in the Finder to be opaque documents. Usually, an application that wants its documents to be document packages uses the NSFileWrapper class to construct and access its documents. If this is true for some or all of the types that your document class supports, then instead of overriding dataOfType:error: and readFromData:ofType:error:, you should override fileWrapperOfType:error: and readFromFileWrapper:ofType:error:.

These methods are much the same as the data-based methods, but they use NSFileWrapper objects instead.

You should also specify, in the document types section of your application’s information property list, that documents of the specified type are packages (as described in “Storing Document Types Information in the Application's Property List”).

How do I implement loading and saving when the simple data or file wrapper API won’t do?

If, for some reason, neither the NSData nor the NSFileWrapper loading and saving API work for you, you can override the three NSDocument methods that load and save a document from a URL. These methods are:

You should override the reading method and one of the writing methods if you are going to use these as your only reading and writing methods.

You might also override these methods not to provide the loading and saving behavior, but rather to do something immediately before or after actually loading or saving. In this case you would add your code before or after a call to the superclass implementation. This type of overriding is discussed in “How can I support reading one type and automatically converting (internally) to another?.”

Should I subclass NSWindowController?

The default Document-based Application project template does not subclass NSWindowController. You do not need to subclass NSWindowController if you are writing a simple application. However, if you are writing an application with more advanced requirements, you will almost certainly want to do so. Here are some common situations that would make subclassing NSWindowController desirable:

For more information on the roles of NSDocument and NSWindowController in the document architecture, see “The Roles of Key Objects in Document Based Applications.”

How do I subclass NSWindowController?

Once you've decided to subclass NSWindowController, you need to make a couple of changes to the default document-based application setup. First, you should add any Interface Builder outlets and actions for your document's user interface to the NSWindowController subclass instead of the NSDocument subclass. This is because, now, the NSWindowController subclass is the nib file's owner. Some menu actions can still be implemented in the NSDocument subclass. For example, save and revert are implemented by NSDocument, and you might add other menu actions of your own such as an action for creating new views on a document.

Second, instead of overriding windowNibName in your NSDocument subclass, override makeWindowControllers. In makeWindowControllers you should create at least one instance of your custom NSWindowController subclass and use addWindowController: to add it to the document. If your document always needs multiple controllers, create them all here. If your document will support multiple views, but by default has one, create the controller for the default view here and provide user actions for creating other views.

You should not force the windows to be visible in makeWindowControllers. NSDocument will do that for you if it’s appropriate.

See “How can I make an NSWindowController subclass that automatically uses a particular nib file?” and subsequent questions for more information about NSWindowController.

When can I do setup operations on my user interface objects?

Many applications need to perform setup operations on user interface objects, such as setting the content of a view, after the application's model data has been loaded. In this case, you must remember that the NSDocument data-reading methods, such as readFromData:ofType:error:, are called before the document's user interface objects contained in its nib file are loaded. Of course, you cannot send messages to user interface objects until after the nib file loads.

After you load the data, you must store it temporarily and set up your user interface objects after the document's nib file loads. If you do not subclass NSWindowController, then you can override the NSDocument method windowControllerDidLoadNib: instead. Or if you do subclass NSWindowController, you should instead override the NSWindowController method windowDidLoad.

If, on the other hand, you need to do some operation immediately before the nib file loads, you can override the NSDocument method windowControllerWillLoadNib: (if you don't subclass NSWindowController) or the NSWindowController method windowWillLoad (if you do subclass NSWindowController).

For objects that are instantiated in the nib file, you can implement the awakeFromNib method for this purpose. The Application Kit sends awakeFromNib to the nib file’s objects after they have all been loaded and all their connections set up. However, the order in which the message is sent is not guaranteed.

How can I support read-only types?

If your application has some types that it can read but not write, you can declare this by setting the role for those types to “Viewer” instead of “Editor” in Xcode, as shown in “Storing Document Types Information in the Application's Property List.”

How can I support write-only types?

If your application has some types that it can write, but not read, you can declare this by using the NSExportableAs key. You can include the NSExportableAs key in the type dictionary for another type that your document class supports. Usually this key would go in the type dictionary for the most native type for your document class. Its value is an array of type names that your document class can write, but not read.

The Sketch example uses this key to allow it to export TIFF and EPS images even though it cannot read those types.

Write-only types can be chosen only when doing Save As operations. They are not allowed for Save operations.

How can I support reading one type and automatically converting (internally) to another?

Sometimes an application might understand how to read a type, but not how to write it, and when it reads documents of that type, it should automatically convert them to another type that you can write. An example of this would be an application that can read documents from an older version or from a competing product. It might want to read in the old documents and automatically convert them to the new native format.

The first step is to add the old type as a read-only type (see “How can I support read-only types?”). By doing this, your application is able to open the old files, but they come up as untitled files.

If you want to automatically convert them to be saved as your new type, you can override the readFrom... methods in your NSDocument subclass to call super and then reset the filename and type afterwards. You should use setFileType: and setFileURL: to set an appropriate type and name for the new document. When setting the filename make sure to strip the filename extension of the old type from the original filename, if it is there, and add the extension for the new type.

How can I customize the Save panel?

You can control whether the default accessory view (which contains a pop-up menu allowing the user to choose what type to save) appears in the Save panel by overriding shouldRunSavePanelWithAccessoryView. The default accessory view is used if that method returns YES and the document supports writing multiple types.

You can customize the panel more completely by overriding prepareSavePanel: and modifying the panel before calling super. For example, you could replace the accessory view entirely.

How do I implement printing?

Subclasses of NSDocument that wish to support printing should override printShowingPrintPanel:. Usually this is implemented to create an NSPrintOperation object with the document's print info and run it.

A document should be prepared to print itself even if it currently has no window controllers.

Should I do anything about print info?

Ideally you should treat a document's print info as part of the document, to be saved and loaded along with the rest of the contents. This is not always possible if your document format is already defined and is not flexible enough to allow saving the print info.

Apart from using it to create your print operations and possibly saving and loading it, you should not have to do anything else about print info.

How can I make an NSWindowController subclass that automatically uses a particular nib file?

An NSWindowController object expects to be told what nib file to load (through its initWithWindowNib... methods), because it is a generic implementation of the default behavior for all window controllers. However, when you write a subclass of NSWindowController, it is almost always designed to control the user interface contained in a particular nib file, and your subclass would not work with a different nib file. It is therefore inconvenient and error-prone for the client of the subclass to have to tell it which nib file to load.

This is easily solved by overriding the init method to simply call the superclass's initWithWindowNibName: method with the correct nib name. Now clients just use init and the controller has the correct nib file. You can also override the initWithWindowNib... methods to log an error, because no clients should ever try to tell your subclass which nib file to use. This is a good idea for any NSWindowController subclass designed to work with a specific nib file. You should do otherwise only if you are extending the basic functionality of NSWindowController in your subclass and have not tied that functionality to any particular nib file.

How can I use NSWindowController for shared panels (inspectors, find panels, etc.)?

An NSWindowController object without an associated NSDocument object is useful all by itself. NSWindowController can be used as the base class for auxiliary panel controllers in order to gain the use of its nib management abilities.

One common standalone use of NSWindowController subclasses is as controllers for shared panels such as find panels, inspectors, or preferences panels. In this case, you can make an NSWindowController subclass that implements a shared instance method. For example, you could create a PreferencesController subclass with a sharedPreferenceController class method that creates a single instance the first time it is called and returns that same instance on all subsequent calls.

Because your subclass derives from NSWindowController, you can just tell it the name of your Preferences nib file and it will handle loading the nib file and managing the window automatically. You add your own outlets and actions, as usual, to hook up the specific user interface for your panel and add methods to manage the panel’s behavior.

The Sketch application uses NSWindowController subclasses for its various secondary panels.

How can I use multiple NSWindowControllers for a single document?

If you use makeWindowControllers to create your window controllers (see “How do I subclass NSWindowController?”), you can create more than one, possibly of different subclasses of NSWindowController, from the beginning. Another possibility is to allow the application to create new controllers later as it needs them. In any event, multiple controllers can be added to a document with addWindowController:.

By default, a document closes when its last remaining window controller closes. Specific window controllers can also be set to close the document when they close even if there are other controllers still open. An example of where this happens is Interface Builder. There is a main window for a nib document with a tab view and top-level instances in it, and there are a number of other windows, which are the window editors for the windows in the nib. If the user closes the main window, all the other windows close as well and the document itself closes. Interface Builder calls setShouldCloseDocument:YES on the main window's controller to implement this behavior.

How can I customize the window title for a document’s NSWindowController?

You can override the NSWindowController method windowTitleForDocumentDisplayName: to modify the title for each view. For instance, a CAD program might have the titles of the different windows it uses for a document named “Airplane” as “Airplane – Top,” “Airplane – Side,” and so on.

How do I implement undo?

Undo is not always easy to implement, but at least the mechanism for implementing it is straightforward. By default, an NSDocument object has its own NSUndoManager object. The NSUndoManager class enables you to easily construct invocations that do the opposite of a preceding change.

The key is to have well-defined primitives for changing your document. Each model object, plus the NSDocument subclass itself, should define the set of primitive methods that can change it. Each primitive method is then responsible for using the undo manager to enqueue invocations that undo the action of the primitive method. For example, if you decide that setColor: is a primitive method for one of your model objects, then inside of setColor: your object would do something like the following:

 [[[myDocument undoManager] prepareInvocationWithTarget:self] setColor:oldColor]

This call causes the undo manager to construct an invocation and save it away. If the user later chooses Undo, the saved invocation is invoked and your model object receives another setColor: message, this time with the old color. (If you're wondering if you have to keep track of whether things are being undone and avoid doing the undo manager stuff again, you don't. In fact, the way redo works is by watching what invocations get registered as the undo is happening and recording them on the redo stack.)

Another piece of good undo implementation is to provide action names so the Undo and Redo menu items can have more descriptive titles. Undo action names are usually best set in action methods instead of the change primitives in your model objects because many primitive changes might go into one user action, or different user actions might result in the same primitives being called in different ways. The Sketch example application implements undo in action methods.

For more information on supporting undo in your application, see Undo Architecture.

How do I implement partial undo?

Because NSUndoManager does multiple-level undo, it is not a good idea to implement undo for only a subset of the possible changes to your document. The undo manager relies on being able to reliably take the document back through history with repeated undos. If some changes get skipped, the undo stack state is no longer synchronized with the contents of the document. Depending on your architecture, that can cause problems that range from merely annoying to fatal.

If there are some changes that you just can't undo, there are two possibilities for handling the situation when a user makes such a change. If you can be absolutely sure that the change has no relationship to any other changes that can happen to the document (that is, something totally independent of all the rest of the contents of the document has changed), then you can safely just not register any undo action for that change. On the other hand, if the change does have some relationship to the rest of the document contents, you should remove all actions from the undo manager when such a change takes place. Such changes then mark points of no return in your user experience. When designing your application and document format, you should strive to avoid the need for these “point of no return” operations.

What if I don't want to support undo?

If you don't wish to support undo at all, the first thing you should do is call setHasUndoManager:NO on your document. This causes the document never to get an undo manager.

Without an undo manager (and undo support from your model objects), the document cannot automatically track its dirty state. So, if you aren't implementing undo, you need to call updateChangeCount: by hand whenever your document is edited.

What's all this change count stuff?

Because of undo support, the document must keep more information than just whether the document is dirty or clean. If a user opens a file, makes five changes, and then chooses Undo five times, the document should once again be clean. But if the user chooses Undo only four times, the document is still dirty.

The NSDocument object keeps a change count to deal with this. The change count can be modified by calling updateChangeCount: with one of the supported change types. The supported changes are NSChangeDone, NSChangeUndone, and NSChangeCleared. The NSDocument object itself clears the change count whenever the user saves or reverts the document. If the document has an undo manager, it observes the undo manager and automatically updates the change count when changes are done, undone, or redone.

If your document subclass does not support undo, then you need to inform NSDocument of edits with updateChangeCount: yourself (see “What if I don't want to support undo?”).

Should I subclass NSDocumentController?

Usually, you should not need to subclass NSDocumentController. Almost anything that can be done by subclassing can be done just as easily by the application’s delegate. However, it is possible to subclass NSDocumentController if you need to.

For example, if you need to customize the Open panel, an NSDocumentController subclass is clearly needed. You can override the NSDocumentController method runModalOpenPanel:forTypes: to customize the panel or add an accessory view.

How can I subclass NSDocumentController?

There are two ways to subclass NSDocumentController:

The first NSDocumentController object to be created becomes the shared instance. The Application Kit itself creates the shared instance (using the NSDocumentController class) during the "finish launching" phase of application startup. So if you need a subclass instance, you must create it before the Application Kit does.

How can I create new documents other than through user-action methods?

You can use the NSDocumentController methods openUntitledDocumentAndDisplay:error: and openDocumentWithContentsOfURL:display:error:, which create a document and, if the display parameter is YES, also create the document’s window controller (or controllers) and add the document to the list of open documents. These methods also check file paths and return an existing document for the file path if one exists.

You can also use the NSDocumentController methods makeUntitledDocumentOfType:error:, makeDocumentWithContentsOfURL:ofType:error:, and makeDocumentForURL:withContentsOfURL:ofType:error:, which just create the document, or you can simply create a document yourself with any initializer the subclass supports. In either case, you usually need to call the NSDocumentController method addDocument: to add the document to the document controller's list of documents

How can I keep my application from creating an untitled document at launch?

Implementing the applicationShouldOpenUntitledFile: method to return NO in your application delegate prevents the application from opening an untitled file when launched or activated. If you do want to open an untitled file when launched, but don't want to open an untitled file when already running and activated from the dock, you can instead implement applicationShouldHandleReopen:hasVisibleWindows: to return NO.



< Previous PageNext Page > Hide TOC


© 2001, 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-01-12)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.