Making NSApplicationDelegate and NSDocument Play Together in OSX 10.8

I found it difficult to setup a NSApplicationDelegate for a Document-based Mac 10.8 (Mountain Lion) application (NSDocument) using ARC.

There's a lot of handy app specific methods in the delegate protocol that you'll most likely want in a new Mac app.  However, the project template (Xcode 4.4) does not include the ApplicationDelegate class. This missing puzzle piece meant I had to figure out how to configure it correctly with Xcode's Interface Builder.

Problem with NSApplicationDelegate

I tried adding an Object as my ApplicationDelegate in my PSDocument.xib file. However, that led to random crashing (EXC_BAD_ACCESS objc_msgSend_vtable5) when I closed windows from my app.

1. The reason it crashed is that the document window was deallocated, and the PSApplicationDelegate object was attached to it. This caused the NSApplication to invoke methods on a previously deallocated object. (i.e. PSApplicationDelegate)

2. Zombies to the rescue. See the screenshot on how to enable zombies if you're new to Xcode. Using zombies I was able to figure out a general idea of what happened.

Zombie Objects

Solution

Add the PSApplicationDelegate object to your MainMenu.xib file. It'll persist until you exit your app, rather than your document windows.

1. Drag the Object (blue-box) from the Object Library to your side panel in Xcode's Interface Builder.

2. Create the NSObject class called PSApplicationDelegate.h/m

3. Conform to the NSApplicationDelegate protocol

#import <Cocoa/Cocoa.h>
 
@interface PSApplicationDelegate : NSObject<NSApplicationDelegate>
 
@end

4. Rename the Object (blue-box) to PSApplicationDelegate

Custom Object in Xcode 4 4 Interface Builder

5. Control-click or right-click and drag from the File's Owner (NSApplication) to the new PSApplicationDelegate object (blue-box)

6. Select delegate.

NSApplicationDelegate Example for Document-based Mac OSX apps

 

Now you're golden and use any of the protocol methods from NSApplicationDelegate.

#import "PSApplicationDelegate.h"
 
@implementation PSApplicationDelegate
 
// Delegate methods
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
    NSLog(@"Started app");
}
 
 
@end

Creating NSDocument using Folder Bundles and UTI Identifiers

I've been working on a Lion NSDocument-based application that deals with multiple data files. I decided to work with the NSFileWrapper and treat a folder like a single file. (i.e. Xcode Project Files .xcodeproj) I wanted to get an icon to display for the folder and to have it appear as a  single file on Finder. In the NSDocument you need to implement the two save/load methods to get started and then you need to edit your Info.plist file.

- (NSFileWrapper *)fileWrapperOfType:(NSString *)typeName
                               error:(NSError *__autoreleasing *)outError {
    // Save data here ...
}

- (BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper
                     ofType:(NSString *)typeName
                      error:(NSError *__autoreleasing *)outError {
    // Load data here ...
}

Problems

There's a few gotchas with the .plist file. In order to create a NSDocument using Folder Bundles you'll need to set the UTI Identifier for the Document  Type and the Exported/Imported UTIs. You'll find this setting on the Info page for your Xcode Target.

 

  1. My App doesn't load saved bundles (NSFileWrapper), instead it just looks inside the folder.Can t Load Data
  2. I tried to undo the Export UTI by deleting it and remove the Identifier, but that doesn't help.
  3. By deleting the Identifier I can't load "New documents".

Solutions

There are two solutions, the second one is more robust, but the first will get you back to when it was working.

Solution 1: Revert back to Xcode template defaults

  1. Remove all Exported UTIs
  2. Open your App .plist file and remove the LSItemContentTypes from your App .plist file. You can't see it in the Target -> Info page.
    1. NOTE: if you don't delete an empty LSItemContentTypes array, then you won't be able to open New files.
    2. If LSITemContentTypes is defined, it'll ignore the "Bundle" checkbox (LSTypeIsPackage) See the Apple docs.
  3. Make sure "Bundle" is checked on the Document Types on your Target's Info page, or (LSTypeIsPackage on .plist file)
    1. This option is magic and auto generates a Identifier for you. (check your save file with "mdls" on the Terminal)
  4. Don't use an Identifier if you check the Bundle option (Document is distributed as a bundle)
  5. Clean the Xcode project

 

Solution 2: Use the package UTI

You'll need to add the correct LSItemContentTypes found in Apple's documentation. I took a look at the Xcode project file with the Terminal utility "mdls" (See sample output below)

  1. Set a unique identifier (Reverse DNS name) com.Your_Company_Name.Project_Name
    1. It needs to match theDocument Type and Exported UTIs
  2. For a bundle, you'll need to set the "Conforms To" for the Exported UTIs to "com.apple.package"
    1. Apple Docs: "A package (that is, a directory presented to the user as a file)"
  3. I noticed Xcode project files also include, public.composite-content
    1. Apple Docs:  "Base type for mixed content. For example, a PDF file contains both text and special formatting data"
  4. NOTE: If you mistype com.apple.package, or leave it out, your file folder will be treated like a public.folder, and you'll get folder behavior.
  5. Clean the Xcode project

Com apple package

Working Icon and Bundle File

If all goes well you'll now see the Icon (icns) file that you created in the Open dialog and the folder will appear to be a bundle.

OpenDialogWorks

Using MDLS to inspect your files

The only way it works as a bundle is if the "com.apple.package" ContentType is listed, otherwise something is wrong. Check your save files with the "mdls" command.

mdls MathAndArt.xcodeproj/

Screen Shot 2012 02 02 at 7 57 23 PM