Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting a basic Cocoa application to a document-based application

Tags:

cocoa

My team and I have been working on an existing, non-document-based Cocoa application. This is our first Cocoa app, although we've done a number of iOS apps thus far.

The app really should be document-based, though, so I've begun trying to convert it. But things here & there don't seem to be working. For example, the File -> Open menu item is permanently disabled (although I finally got the File -> Save menu item to enabled; initially it wouldn't). In addition, I can click the red X to close a window, although the File -> Close menu item itself is disabled; however, when I close the window via the X button, the dealloc method in my NSDocument implementation (SPDocumentInfo) is not invoked. I created a sample, brand-new document-based app just for comparisons; when I close a window there, the SPDocument implementation's dealloc method is indeed invoked (as I'd expect.) So that concerns me.

I made a lot of changed to the project here and there; they include:

  • Made SPDocumentInfo extend SPDocument like so in the .h file:

    @interface SPDocumentInfo : NSDocument <NSWindowDelegate>
    
  • Implemented the following in SPDocumentInfo:

    - (NSString *)windowNibName {
        return @"SPDocument";
    }
    
    - (void)windowControllerDidLoadNib:(NSWindowController *) aController {
        [super windowControllerDidLoadNib:aController];
    }
    
    - (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
        NSString *xml = [self toXml];
        return [xml dataUsingEncoding:NSUTF8StringEncoding];
    }
    
    - (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
        // will make this work later
        if ( outError != NULL ) {
            *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL];
        }
        return YES;
    }
    
  • Edited the .plist file to add "Document types". Among other things, defined "Cocoa NSDocument Class" = "SPDocumentInfo".

  • Altered some connections in SPDocumentInfo to match the connections in the sample document-based app. For example, in SPDocument.nib, the File's Owner (which represents SPDocumentInfo) is the Window's delegate.

So, I'm wondering if there are other sorts of things I might be missing in converting to a doc-based app. Or, are there any guides on how to do this? (I've looked but couldn't find any). Or should I just start over with a new document-based app and try to retrofit all of our stuff into it? In general, does anyone have any experience with this?

like image 273
Dave Taubler Avatar asked Feb 23 '11 22:02

Dave Taubler


4 Answers

Everything @Hans' answer provides is correct, but the final change that is needed is totally, stupidly trivial, but adds a nontrivial amount of functionality that comes with document-based apps:

In the Main.storyboard file, the document element has an extra property that needs to be deleted: initialViewController="XXX-XX-XXX".

It is probably the last thing on second line. Remove this, and the Save… menu option, along with a few other menu options, will properly be enabled by default, and the app will properly recognize the document object upon launch.

like image 153
Carter Pape Avatar answered Nov 03 '22 06:11

Carter Pape


An often found suggestion is to create a new document based application and move all you existing code in there. This can be cumbersome for a large workspace with all kinds of stuff nicely configured. Let alone breaking version control.

I took the following simple steps and it worked:

  • Generate a document based application
  • from this this generated project, copy the following section from the Info.plist (open the file with a normal text-editor):

    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeExtensions</key>
            <array>
                <string>mydoc</string>
            </array>
            <key>CFBundleTypeIconFile</key>
            <string></string>
            <key>CFBundleTypeName</key>
            <string>DocumentType</string>
            <key>CFBundleTypeOSTypes</key>
            <array>
                <string>????</string>
            </array>
            <key>CFBundleTypeRole</key>
            <string>Editor</string>
            <key>NSDocumentClass</key>
            <string>$(PRODUCT_MODULE_NAME).Document</string>
        </dict>
    </array>
    

    and paste it in the Info.plist file in your own project.

  • Copy Document.swift from the generated document-based project into your own project.

  • it contains a method:

    override func makeWindowControllers() {
        // Returns the Storyboard that contains your Document window.
        let storyboard = NSStoryboard(name: "Main", bundle: nil)
        let windowController = storyboard.instantiateController(withIdentifier: "Document Window Controller") as! NSWindowController
        self.addWindowController(windowController)
    }
    

    It creates a new window just the way you application normally would. If you storyboard has only one windowcontroller the 'withIDentifier'-field can contain something arbitrary. If you have more window controllers in your storyboard, the identifier needs to correspond to the right windowcontroller for new documents.

like image 35
Hans Avatar answered Nov 03 '22 06:11

Hans


Okay, this time I legitmately do have a solution to present.

It turns out I had a "window" instance variable in SPDocumentInfo (which as you'd guess pointed to the NSWindow associated with the document). That appeared to caused a chain of events (or more likely, prevented a chain of events) which led to SPDocumentInfo's dealloc not being called when it should have. I didn't catch that when I was comparing my project to the sample doc-based project, because apparently SPDocument also has a member variable called "window" which is also connected to the relevant NSWindow. I saw that connection in the sample project, and it looked identical to my project's connection, so I didn't think twice about it.

In other words, part of my problem was that I just coincidentally decided to connect up a "window" outlet NSDocument implementation, and didn't realize that I was actually shadowing a superclass variable (which I'm guessing is, unlike mine, configured as "assign" and not "retain").

So, things seem okay at this point, and I think I can declare that it is indeed possible (and my nagging issue notwithstanding, generally painless) to convert from a non-doc-based app to a doc-based one.

like image 4
Dave Taubler Avatar answered Nov 03 '22 05:11

Dave Taubler


This is more of an opinion than a direct answer, but if you're new to the Mac side and to document-based applications, your path of least resistance would definitely be to create a new doc-based Xcode project from the template and move your relevant code over, plugging it into the template places where needed.

like image 3
Joshua Nozzi Avatar answered Nov 03 '22 06:11

Joshua Nozzi