How does Xcode setup a document based application? - xcode

I am learning Cocoa and my understanding from reading the documentation is that when an application starts the following happens:
A shared application instance is created.
The main nib file is looked up from the applications property list so that the application knows which nib to load.
the run loop is started.
This is fine and makes sense for s single windowed application however I am confused by what xcode does when a document based application is created.
In this case there are two nib files; the first contains the application menu and the second contains the window which represents the NSDocument subclass. when I run the application a new document window is opened automatically.
Based on my understanding of how the application works outlined above I don't understand how my application knows to open the document window once the menu nib has been looked up from the property list. There is no code generated to do this as far as I can see (except for the windowNibName method but where is this called from?)
Can anyone tell me what xcode does differently so that the application knows that it is document based and therefore needs to open a document window?
Update:
What I am trying to understand is how Xcode knows how to do something different if my application is set up as a document based application rather than a single window application. As far as I am aware there is no setting to specify this and Xcode doesn't appear to generate any code to give this different behaviour.
From reading the documents over the last couple of days I think I know how this works but am not sure:
_NSApplication_has a delegate method applicationOpensUntitledFile which is called by the applications delegate.
NSDocumentController is set as the applications delegate by default and the default implementation looks for the presence of the CFBundledTypeInfo to determine if the document is document based or not and responds as is appropriate for the application (I.E. YES for document based application and NO for single window applications).
The majority of the time when a single window application is created the application delegate is replaced by a custom AppController anyway which usually wont contain a definition for the applicationOpenUntitledFile method as it is not appropriate for the type of application.
Hopefully any Cocoa experts can confirm if my understanding is correct or if I am barking up the wrong tree.

When you create a document-based application, you get a few things:
A subclass of NSDocument
A new xib file for this document, in addition to MainMenu.xib
A CFBundleDocumentTypes entry in Info.plist, which tells the app about your NSDocument subclass
When your app opens, the shared NSDocumentController will create a new untitled document using the CFBundleDocumentTypes information.
For more information, read The Document-Based Application Project Template and the rest of the document-based applications guide.

I assume your right. If you create a non based document application, add the document types informations in the -Info.plist and set the delegate of NSApplication in the main.m as following
int main(int argc, const char * argv[])
{
[[NSApplication sharedApplication] setDelegate:[NSDocumentController sharedDocumentController]];
[[NSBundle mainBundle] loadNibNamed:#"MainMenu" owner:NSApp topLevelObjects:nil];
[NSApp run];
}
The behaviour seems to be the same as the the default Document-Based Application template.

No, your assumption is not right, look at the implementation of GNUstep version, in the NSApplication's finishLaunching method:
NSDocumentController *sdc;
sdc = [NSDocumentController sharedDocumentController];
if ([[sdc documentClassNames] count] > 0)
{
didAutoreopen = [sdc _reopenAutosavedDocuments];
}
So it create a instance of NSDocumentController automatically.

Related

Loading main window at applicationDidFinishLaunching in Cocoa Application

In a Cooca Application the MainMenu.xib is setup for you in the standard template. This nib has been setup with the application delegate too. In the info.plist the key "Main nib file bas ename" sets the nib file to load at startup.
I want the application to start if possible without a nib, I want to load the MainMenu.xib at applicationDidFinishLaunching in the application's main delegate.
Is it possible?
First, comment out NSApplicationMain in supporting files -> main.m. NSApplicationMain() loads the main nib mentioned in your Info.plist, so skip it. Instead, setup the app and delegate and run the application:
int main(int argc, const char * argv[])
{
//return NSApplicationMain(argc, argv);
#autoreleasepool {
NSApplication * application = [NSApplication sharedApplication];
MYAppDelegate* appDelegate = [[MYAppDelegate alloc] init];
[application setDelegate:appDelegate];
[application run];
}
return EXIT_SUCCESS;
}
Then, in the app delegate's applicationDidFinishLaunching: function, call something similar to createMainWindow:
- (void)createMainWindow
{
self.wincon = [[MYCustomWindowController alloc] initWithWindowNibName:#"MainMenu"];
self.window = self.wincon.window; // window property in appdelegate created for single-view app
// Also had to connect About: to application's orderFrontStandardAboutPanel
}
MainMenu.xib's File's Owner custom class should be switched to MYCustomWindowController from the application.
If MainMenu.xib has a window like in this example, it's "referencing outlet" needs to be connected to File's Owner->window.
If you started with a single view application, DELETE the App Delegate object from MainMenu.xib -- otherwise the xib will create a second instance of your app delegate. This can be terrible if you're referencing something like MYAppDelegate.managedObjectContext. If you need to bind to the application delegate, you can bind to the Application with a key path of delegate.managedObjectContext.
Why did I do this? Because sometimes my application launches with a GUI, and sometimes it doesn't.
This is possible, but seldom worth the trouble IMO. If you have a bundle already there is little cost in including a small nib file (menu only; no window). If you want to load the rest of your UI from a separate nib file after launch, that's fine. But I recommend allowing MainMenu.nib to load and provide the main menu. (You're not clear on what problem you're trying to solve with your approach.)
That said, Lap Cat wrote a series of articles on this called "Working without a nib" that's worth reading. You'll want the last article The Empire Strikes Back where he includes the link to his nibless project. His technique still works in 10.7.

NSWindowRestoration issue

I am having a difficult time implementing this new "feature" of Mac OS X 10.7. For the most part, my application works without my having to do anything. Files reopen on launch as expected. If the file is deleted however, my application opens to nothing and a new, blank document needs to be opened via the File menu.
So, what I have done so far is when a new window is created, I call
[myWindow setRestorationClass:(Class < NSWindowRestoration >)self];
with self being my NSDocument class.
Since restoreWindowWithIdentifier:state:completionHandler: is a class method I can't call my windowController creation method [self makeWindowControllers] from within it nor could I call [self initWithType:error] to create a new document if the one being sent has been deleted. How does one tell if the document being sent has been deleted from within this method?
I've read all I can find on Apple's site and elsewhere on this issue and am getting nowhere. Realize my core understanding of this is lacking and I apologize for that. I appreciate any help. Thank you.
As far as I understand the problem I think you have to enable in your app delegate
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender;
{
return YES;
}
Did you try that?

Xcode 4.2 Template Changes - UIApplication & MainWindow.xib

Background: Up until Xcode 4.2, new projects created using any of the templates would contain a MainWindow.xib and therefore pass nil as the fourth argument of UIApplicationMain(). Starting in Xcode 4.2 all the templates instantiate the application delegate by passing the class string as the fourth argument and do not build the application's window in a xib.
It is trivial to accomplish this setup in 4.2, and of course it works as expected: create xib setting File's Owner to UIApplication and wire up the delegate, specify it in Info.plist, nil fourth argument in main().
Question: Why is Apple encouraging instantiating the application delegate and building the UIWindow in code now instead of the "old way?" What are the benefits?
Considerations: I would expect this new template behavior if you elect to use storyboarding as a way to manage the UI, but if you uncheck "Use Storyboards" I would have expected the old pass-nil-and-use-MainWindow.xib template.
This question was asked in a roundabout way here, but the answers are a little thin on discussion.
You're asking why Apple is doing something? There can be no definitive answer, unless Apple has spoken out explicitly, which they have not done.
Personally I find the new approach considerably more elegant, transparent, and bulletproof. As you rightly say, in the old approach the main nib was loaded automatically by the runtime in response to the Info.plist setting, and everything else that happened was done through the nib, in particular the instantiation of the app delegate and the window and the associated wiring (the app delegate must be made the application delegate, the window must be made the app delegate's window), except that then we come back to the code in the app delegate for final presentation of the interface.
This was hard to understand; it took a great deal of verbiage for me to describe it in my book. It was also easy to break. The nib has to know the name of the app delegate class, so if you didn't like those funny long names that were created by default, you could easily mess everything up when you changed them.
Now, however, the app delegate is simply named App Delegate and is instantiated in code by UIApplicationMain(), as you rightly say; and everything else is also done in code as a direct follow-on: the app delegate is instantiated and didFinishLaunching is called, whereupon we create the window in code, assign it to our property in code, load the nib if there is one in code, set the window's rootViewController in code, and show the interface in code as before.
Thus the bootstrapping is directly exposed to view because it's all code. This makes it easier to understand and to modify without breaking anything. It's almost as if previously the template designer was just showing off as to how much stuff could be made to happen magically and automatically behind the scenes; now everything happens out in the open, explicitly.

Core Data template: Data Not Persisted Between Runs

I created a small test Mac app using the Core Data template (on Lion 10.7 and Xcode 4). I used the example on this site, http://www.swampfoetus.net/chapter-7-fail/, to hook up all the Cocoa Bindings with a tableview, an NSArrayController, a text box and an Add button. The NSArrayController is linked to the managedObjectContext of the App Delegate.
Everything seems to work fine when I launch the app ... I can type in text and press Add, and it gets saved in the tableview. I saved a few rows, and then pressed Save in the file menu (linked to the saveAction IBAction) and quit the app. I can see the data being saved in the xml data file (I renamed it .xml ... the PSC is of type NSXMLStoreType).
The problem is that when I launch the app again, it launches without the data that was saved in the Core Data file in the previous run.
This happens each time ... I can add data and it keeps appending to the data file, but at launch it never seems to read from this data file.
Any ideas what could be wrong here? I haven't messed around with the App Delegate generated code at all, only set up the bindings which seem to work fine. What could I check to make sure it's setup correctly?
If the data shows up in the persistent store, then the only explanation would be a problem with the binding where the UI doesn't display the previous data for some reason. It's hard to say why that is happening but my guess would be a fetch predicate or some other bound qualifier that causes the controller to ignore older objects so that they are not displayed.
I can't say for sure because I don't have access to the book.
This is one of the drawbacks of using bindings. When they work, they're fantastic but when they don't, they're a ##%! to debug.

Dropping Files onto Dock Icon in Cocoa

How can I drop a file(or select to open it in Finder) of a type specified in the Info.plist onto my dock icon and then calling a method with the full path of the file?
If you've set up your Info.plist's CFBundleDocumentTypes array properly (either 'LSItemContentTypes' or 'CFBundleTypeExtensions'), then you just need to set up an NSApplication delegate and implement the delegate method, application:openFile:.
If you're expecting multiple files to be dropped at once, implement application:openFiles:.
For promised files (NSFilesPromisePboardType/kPasteboardTypeFileURLPromise) see Dropping promised files on to application icon in Dock.
Here's an updated solution for Xcode 5.
In AppDelegate.m
-(BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
{
NSLog(#"%#", filename);
return YES;
}
And in Xcode setup Document Types under Project > Targets > Info:
Check settings in Info.plist in case you have an empty 'Document Content Type UTIs' array which should be filled out properly or else deleted.
Your Info.plist should look something like this:
On current systems you can use a UTI instead of the old-style four-char types (such as fold above). In Xcode's document type editor, make a new type with:
Name: Folder
Identifier: public.folder
public.folder is a subtype of public.directory. public.folder matches directories that appear as such to the user, i.e. not packages like .app wrappers.
Select your application in the target group of the side pane and use get info. Then in the new window select the properties tab to add a new document type. Name it "Folder" for convenience and the OS Types needs to be "fold"; the store type and role you can leave as is.
If you're actually making a document-based app, setting it up to give you the path will have you doing far more work than you need to. Simply use the document-based application template. The document controller will create an instance of the right class for you; you need only write that class.
An application you create this way will handle file drops (by opening them as documents) for free.

Resources