It is possible to put together a document-based application without having to write much code. If your requirements are minimal, you can use the default NSWindowController
instance and default NSDocumentController
instance provided by the Application Kit. You have only to create a document project, compose the human interface, implement a subclass of NSDocument
, and add any other custom classes or behavior required by your application.
The following sections step you through the tasks you must do, and those you might want to do, when implementing a document-based application. Where something is described in detail elsewhere, such as overridden methods in NSDocument
subclasses, you are referred there.
As for the three classes behind document-based applications, two likely concerns are the number of required objects and whether subclassing is necessary. The following table summarizes this information:
Class | How many objects? | Subclass? |
---|---|---|
| 1 per application | Optional (but unlikely) |
| 1 per document | Required |
| 1 per window | Optional (but likely) |
Cocoa’s development environment provides a Cocoa Document-based Application project template in Xcode to expedite the development of document-based applications. This project template provides the following things:
A nib file for the application's document
This nib file is named MyDocument.nib
. A subclass of NSDocument
named MyDocument
is made file’s owner of the nib file. It has an outlet named window
connected to its window object. The window has only a text field on it with the words "Your document contents here".
The application’s main nib file
This nib file is named MainMenu.nib
. It contains an application menu, a File menu (with all of its associated document commands), and an Edit menu with text editing commands and Undo and Redo menu items. These menu items, as well as all of the menu items of the File menu, are connected to the appropriate first-responder action methods. The About NewApplication menu item is connected to the orderFrontStandardAboutPanel:
action method that displays a standard About window.
A skeletal NSDocument
subclass implementation
The project includes MyDocument.h
and MyDocument.m
. The latter file includes empty but commented blocks for the dataRepresentationOfType:
and loadDataRepresentation:ofType:
methods. These skeletal implementations are for applications targeted for systems that must be able to run on Mac OS X v10.3 and earlier. For applications that can require Mac OS X v10.4 and later, you should instead override the dataOfType:error:
and readFromData:ofType:error:
methods. It also includes a fully implemented windowNibName
method that returns the name of the document window nib file, MyDocument
, and an override of windowControllerDidLoadNib:
.
The application's information property list
In the Xcode Target inspector, you can edit the file Info.plist
, which contains placeholder values for global application keys, as well as for the CFBundleDocumentTypes
key (which specifies information about the document types the application works with).
The steps described in the following sections show how to create a document-based application using Xcode’s Cocoa Document-based Application project template. The following table lists the File menu first-responder action connections that should already exist in the template application.
File menu command | First-responder action |
---|---|
New |
|
Open... |
|
Open Recent > Clear Menu |
|
Close |
|
Save |
|
Save As... |
|
Revert |
|
Page Setup... |
|
Print... |
|
The template has similar ready-made connections for the Edit, Window, and Help menus. If your application does not support any of the supplied actions, such as printing, for example, you should remove the associated menu items from the nib.
Launch Xcode and choose New Project from the File menu.
In the New Project dialog, choose "Cocoa Document-based Application."
Provide a name and location on disk for the project.
Double-click the MyDocument.nib
file in the Resources group in Xcode’s Groups & Files pane. This opens the file in Interface Builder.
If you want to change the name of the nib file, you can save it under another name in Interface Builder and add it to the project. If you do this, you must also modify the string returned by the windowNibName
method in the NSDocument
subclass implementation.
Create the visible interface for the document window in Interface Builder.
If the objects on the document window require further outlets or actions, add these to the MyDocument subclass of NSDocument
. Connect these actions and outlets via the File’s Owner icon on the Instances display of the nib file window.
If you want to name your NSDocument
subclass something other than MyDocument
(the default name), change the name in Interface Builder and wherever it occurs in the Document header (.h
) and implementation (.m
) files. You must also change the name under the NSDocumentClass
key in the Info.plist
file.
If your document objects interact with other custom objects, such as model objects that perform specialized computations, define those objects in Interface Builder and make any necessary connections to them.
In Xcode, click the Targets group disclosure triangle, select the application target, and click the Info button in the Xcode toolbar (or choose Get Info from the File menu). Click the Properties tab in the Target Info window.
Replace the placeholder or default values in the information property list with those specific to your application. You can also edit the Info.plist
file directly.
See “Storing Document Types Information in the Application's Property List” and “Creating Multiple-Document-Type Applications” for information on the properties specific to documents.
See also "Inspecting Targets" in Targets for information about the fields available in the Xcode Target inspector.
The following procedure just gives general guidelines. For more details see the NSDocument
reference and “Creating a Subclass of NSDocument.” You might also read the Cocoa documentation covering undo, copy/paste, and printing.
In Xcode, open the header file of your NSDocument
subclass (in the Classes group in Xcode’s Groups & Files pane).
If you added outlets or actions to your NSDocument
subclass in Interface Builder, add them to the subclass’s header file. Also add any other required instance variables and include the declarations of new methods that you wish to be public, such as accessor methods.
You can specify additional outlets in actions in the existing header file and then import them into the nib file by using Interface Builder’s Read Files command in the Classes menu.
Open the subclass implementation file in the project's Classes group.
Although it’s not usually necessary, you can override the designated initializer (init
) and perhaps the document-opening initializer initWithContentsOfFile:ofType:
to perform initializations specific to your subclass; be sure to invoke the superclass implementations. You can also implement awakeFromNib
to initialize objects unarchived from the document’s window nib files (but not the document itself).
Override the data-based reading and writing methods. For applications targeted for Mac OS X v10.4 and later, override readFromData:ofType:error:
(to load document data of a certain type) and dataOfType:error:
(to provide document data of a certain type).
The following example implementations assume that the application has an NSTextView
object configured typically with an NSTextStorage
object to hold the document's data. The NSDocument
object has text
and setText:
accessors for the document's NSAttributedString
data model.
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError { |
BOOL readSuccess = NO; |
NSAttributedString *fileContents = [[NSAttributedString alloc] |
initWithData:data options:nil documentAttributes:nil |
error:outError]; |
if (fileContents) { |
readSuccess = YES; |
[self setText:fileContents]; |
[fileContents release]; |
} |
return readSuccess; |
} |
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError { |
[textView breakUndoCoalescing]; |
NSData *data = [textView dataFromRange:NSMakeRange(0, [[textView textStorage] length]) |
documentAttributes:nil |
error:outError]; |
if (!data && outError) { |
*outError = [NSError errorWithDomain:NSCocoaErrorDomain |
code:NSFileWriteUnknownError userInfo:nil]; |
} |
return data; |
} |
For applications that must be able to run on Mac OS X v10.3 and earlier, you should implement loadDataRepresentation:ofType:
and dataRepresentationOfType:
instead.
For applications targeted for Mac OS X v10.4 and later, If your application needs access to document data files, you can override readFromURL:ofType:error:
and writeToURL:ofType:error:
, respectively, instead. You can override writeToURL:ofType:forSaveOperation:originalContentsURL:error:
instead of writeToURL:ofType:error:
if your document writing machinery needs access to the on-disk representation of the document revision that is about to be overwritten. If your document data is stored in file packages, you can override readFromFileWrapper:ofType:error:
and fileWrapperOfType:error:
instead.
Here are examples of URL-based reading and writing implementations which have the same assumptions as the previous examples.
- (BOOL)readFromURL:(NSURL *)inAbsoluteURL ofType:(NSString *)inTypeName error:(NSError **)outError { |
BOOL readSuccess = NO; |
NSAttributedString *fileContents = [[NSAttributedString alloc] |
initWithURL:inAbsoluteURL options:nil |
documentAttributes:NULL error:outError]; |
if (fileContents) { |
readSuccess = YES; |
[self setText:fileContents]; |
[fileContents release]; |
} |
return readSuccess; |
} |
- (BOOL)writeToURL:(NSURL *)inAbsoluteURL ofType:(NSString *)inTypeName error:(NSError **)outError { |
NSData *data = [[self text] RTFFromRange:NSMakeRange(0, |
[[self text] length]) documentAttributes:nil]; |
BOOL writeSuccess = [data writeToURL:inAbsoluteURL |
options:NSAtomicWrite error:outError]; |
return writeSuccess; |
} |
For applications that must be able to run on Mac OS X v10.3 and earlier, you should override readFromFile:ofType:
and writeToFile:ofType:
instead.
Implement the method to create the window controller or controllers for the NSDocument
object.
If your document has only one window, the project template provides a default implementation:
- (NSString *)windowNibName { |
return @"MyDocument"; |
} |
If your document has more than one window, or if you have a custom subclass of NSWindowController
, override makeWindowControllers
instead. Make sure you add each created window controller to the list of such objects managed by the document using addWindowController:
. This method causes the document to retain the window controller object, so be sure to release it after you add it.
You can implementwindowControllerWillLoadNib:
and windowControllerDidLoadNib:
to perform any necessary tasks related to the window before and after it is loaded from the nib file.
Here is an example:
- (void)windowControllerDidLoadNib:(NSWindowController *)windowController { |
[super windowControllerDidLoadNib:windowController]; |
[textView setAllowsUndo:YES]; |
if (fileContents != nil) { |
[textView setString:fileContents]; |
fileContents = nil; |
} |
} |
Mark the document’s dirty flag when it is edited.
The flag returned by isDocumentEdited
indicates whether the document has unsaved changes. Although the NSDocument
object clears this flag when it saves or reverts a document, you must set this flag in your code, unless you are using the NSDocument
object’s default undo/redo mechanism. Normally, you respond to the appropriate delegation or notification messages sent when users edit a document, then invoke updateChangeCount:
with an argument of NSChangeDone
to set the dirty flag.
Write the code that prints the document’s data.
If you want users to be able to print a document, you must override printOperationWithSettings:error:
, possibly providing a modified NSPrintInfo
object.
Register undo and redo groups in your code. See the class description of NSUndoManager
for details.
And, of course, you must implement any methods that are special to your NSDocument
subclass.
If the default NSWindowController
instance provided by the Application Kit does not meet the needs of your document-based application, you can create a custom subclass of it. If you do so, you must override the NSDocument
makeWindowControllers
method to instantiate this custom class and add the created object to the document’s list of window controllers. You should also ensure that your NSWindowController subclass is the nib file's owner.
If the default NSDocumentController
object somehow does not meet all of your requirements for an application controller, such as handling user preferences or responding to uncommon application delegate messages, usually you should create a separate controller object (instead of subclassing NSDocumentController
). For information on implementing NSDocumentController
and NSWindowController
subclasses, refer to the appropriate class documentation. See also “Creating a Subclass of NSDocumentController” and “Frequently Asked Questions” in this document.
Last updated: 2010-03-24