Cocoa Programming for Mac OS X - Aaron Hillegass [56]
• Taking user input from the views and updating the model
Info.plist and NSDocumentController
When it builds an application, Xcode includes a file called Info.plist. (Later in this chapter, you will change Info.plist.) When the application is launched, it reads from Info.plist, which tells it what type of files it works with. If it finds that it is a document-based application, it creates an instance of NSDocumentController (Figure 10.2). You will seldom have to deal with the document controller; it lurks in the background and takes care of a bunch of details for you. For example, when you choose the New or Save All menu item, the document controller handles the request. If you need to send messages to the document controller, you could get to it like this:
NSDocumentController *dc;
dc = [NSDocumentController sharedDocumentController];
Figure 10.2. Document Controller
The document controller has an array of document objects—one for each open document.
NSDocument
The document objects are instances of a subclass of NSDocument. In your RaiseMan application, for example, the document objects are instances of RMDocument. For many applications, you can simply extend NSDocument to do what you want; you don’t have to worry about NSDocumentController or NSWindowController at all.
Saving
The menu items Save, Save As..., Save All, and Close are all different, but all deal with the same problem: getting the model into a file or file wrapper. (A file wrapper is a directory that looks like a file to the user.) To handle these menu items, your NSDocument subclass must implement one of three methods:
- (NSData *)dataOfType:(NSString *)aType
error:(NSError **)e
Your document object supplies the model to go into the file as an NSData object. NSData, essentially a buffer of bytes, is the easiest and most popular way to implement saving in a document-based application. Return nil if you are unable to create the data object, and the user will get an alert sheet indicating that the save attempt failed. Note that you are passed the type, which allows you to save the document in one of several possible formats. For example, if you wrote a graphics program, you might allow the user to save the image as a gif or a jpg file. When you are creating the data object, aType indicates the format that the user has requested for saving the document. If you are dealing with only one type of data, you may simply ignore aType. To signal that you were unable to save the data, return nil and create an NSError object that describes what went wrong.
- (NSFileWrapper *)fileWrapperOfType:(NSString *)aType
error:(NSError **)e
Your document object returns the model as an NSFileWrapper object. It will be written to the filesystem in the location chosen by the user. To signal that you were unable to create the file wrapper, return nil and create an NSError object that describes what went wrong.
- (BOOL)writeToURL:(NSURL *)absoluteURL
ofType:(NSString *)typeName
error:(NSError **)outError;
Your document object is given the URL and the type and is responsible for storing the data into the URL. (The URL is typically just a file on the filesystem.) Return YES if the save is successful and NO if the save fails. Return NO signal that you were unable to write the data to the URL, and create an NSError object that describes what went wrong.
NSError can be a bit confusing. The idea is that if the method is unable for some reason, to do its job, the method creates an NSError and puts a pointer to that error in the supplied address. For example, to read an NSData from a file, you would supply an address where the pointer to the error would be placed:
NSError *e;
NSData *d = [NSData dataWithContentsOfFile:@"/tmp/x.txt"
options:0
error:&error];
// Did the read fail?
if (d == nil) {
NSLog(@"Read failed: %@", [error localizedDescription];
}
Thus, NSData will either return a data object or create an error object.
In these save and load methods, you will be responsible for creating an NSError if