< Previous PageNext Page > Hide TOC

Implementing a Document-Based Application

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?

NSDocumentController

1 per application

Optional (but unlikely)

NSDocument

1 per document

Required

NSWindowController

1 per window

Optional (but likely)

Contents:

The Document-Based Application Project Template
Create the Project and Compose the Interface
Complete the Information Property List
Implement the NSDocument Subclass
Implement Additional Controller Classes


The Document-Based Application Project Template

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:

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

newDocument:

Open...

openDocument:

Open Recent > Clear Menu

clearRecentDocuments:

Close

performClose:

Save

saveDocument:

Save As...

saveDocumentAs:

Revert

revertDocumentToSaved:

Page Setup...

runPageLayout:

Print...

printDocument:

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.

Create the Project and Compose the Interface

  1. Launch Xcode and choose New Project from the File menu.

    In the New Project dialog, choose "Cocoa Document-based Application."

  2. Provide a name and location on disk for the project.

  3. 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.

  4. Create the visible interface for the document window in Interface Builder.

  5. 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.

    Important: Do not generate an instance of MyDocument to make these connections.

    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.

  6. 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.

Complete the Information Property List

  1. 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.

  2. 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.

Implement the NSDocument Subclass

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.

  1. In Xcode, open the header file of your NSDocument subclass (in the Classes group in Xcode’s Groups & Files pane).

  2. 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.

  3. Open the subclass implementation file in the project's Classes group.

  4. 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).

  5. 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:NULL documentAttributes:NULL
                error:outError];
        if (fileContents) {
            readSuccess = YES;
            [self setText:fileContents];
            [fileContents release];
        }
        return readSuccess;
    }
     
    - (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
        NSData *data = [textView RTFFromRange:NSMakeRange(0,
                                            [[textView textStorage] length])];
        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.

  6. 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.

  7. 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.

  8. 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)windowControllerDidLoadWindowNib:(NSWindowController *)windowController {
        [super windowControllerDidLoadWindowNib:windowController];
        [textView setAllowsUndo:YES];
        if (fileContents != nil) {
            [textView setString:fileContents];
            fileContents = nil;
        }
    }
  9. 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.

  10. 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.

  11. 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.

Implement Additional Controller Classes

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.



< 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.