Starting a Cocoa document-based application shows selection window first - cocoa

This seems like it should be easy yet I must be missing something. I have a document-based application. I have also built a new XIB that has a NSTableView and three buttons on it that I intend to display a list of previous files. I wish this XIB to be displayed instead of the document window when the application first starts. Once the user selects an old file or hits the "New" button I wish to then go to the document window. This is very common and I've seen used quite often.
In my attempts to get this working I have modified the project-info.plist file and changed the Main NIB File Base Name from MyDocument to my Selection XIB name. This causes the application to display the Selection window instead of the MyDocument window. There seems to be no problem up to this point.
In my Selection window I have set up my table view and an array controller and a custom window controller just for this XIB. I have set the File's Owner to the new window controller and bound the window controller's window property to the window and the Window's delegate property to the File's Owner as well as the "Select", "Cancel", and "New" buttons. Nothing is bound to NSApplication. But the strange thing is when I run this application it seems to want to connect these controllers to NSApplication with the error (same for the other two buttons):
Could not connect the action selectButton: to target of class NSApplication
It also displays an error that NSApplication is not Key-Value compliant for the outlet that holds reference to my array. The Array Controller, Window, and buttons are not bound to NSApplication but to the new Window Controller. I would have expected that if there was any problem is would NOT mention NSApplication but rather the window controller to which the controllers are bound.
Anyone know what is happening here? Is this a Target-Action problem because I changed the "Main NIB File Base Name" from "Main Menu" to "Selection"? If I am not supposed to change this, then how can I get Cocoa allow me to display a selection screen before showing the document window?
Any help is greatly appreciated.
Rob

The setting in IB for the class of the File's Owner of the nib is only advisory; it lets IB show only the outlets and actions that are provided by instances of that class. It does not enforce that the File's Owner will be an instance of that class, because the File's Owner is not part of the nib.
The File's Owner is the object that loads the nib. This necessarily means that it is outside of the nib, and nothing in the nib determines anything about it. In the case of the MainMenu nib, its File's Owner—the object that loads the MainMenu nib—is the NSApplication instance. So, everything you hooked up to the File's Owner in your MainMenu nib, you hooked up to the application object, even though you told IB that it wouldn't be the application.
That the application is the owner of the MainMenu nib—regardless of what you tell IB—is not the bug. The application is always the owner of the MainMenu nib. That is normal and correct; you cannot change it, should not attempt to change it, and don't need to change it.
The bug, in a nutshell, is that you are using one nib for two very different purposes.
You should let the MainMenu nib be that alone—containing only the MainMenu, your custom document controller (I'll get to that in a moment), and your app delegate—and move the previous-documents window into a separate nib, owned by the previous-documents window controller. In order to have a window controller be the owner of this nib, you need to have the window controller load it. You must do that in code—you cannot set that up in IB or in a plist.
In your application's delegate, instantiate and own the window controller. It sounds like you made a custom NSWindowController subclass, so you can override its init to have it send itself the initWithWindowNibName: message to load and own the nib. Then, just use alloc and init to create the window controller from the app delegate.
That will get rid of the console message, and ensure that the buttons are actually hooked up to the window controller (because they're hooked up to the File's Owner, which, with this change, will be the window controller).
Have your app delegate respond to applicationOpenUntitledFile: by sending the window controller the showWindow: message. This will make the previous-documents window appear any time the user ordinarily would have created a new document.
If you want to support the usual methods of creating documents (i.e., allow New Document to work), then implement applicationDidFinishLaunching: and applicationShouldHandleReopen:hasVisibleWindows:, not applicationOpenUntitledFile:. Make sure no documents are open, and show your window if that's the case.
You should also make a custom subclass of NSDocumentController and make your document controller an instance of that, and in that class, implement addDocument: and removeDocument: to re-show the previous-documents window when the last open document is closed, and hide it when a document is opened.

Related

How do you create a NSWindowController for a xib-based (not storyboard-based) app?

I have an application that's not based on storyboards, but rather xib files. Main.xib contains the main application window. However, it's just a window. There's no NSWindowController. How can one be added?
Ok, figured it out. You simply add a new object to the scene, change its class to whichever NSWindowController subclass you want it to work with, attach the window to its output, then set an output to hold the window controller itself. I recommend on the app delegate.
I took things a step further by also changing the window to not show on launch, and I removed the window from the App Delegate (since it now has a reference to the window controller and thus the window indirectly already). This way I can center the window before actually showing it.
Only caveat to look out for is that you won't get the window-loading overrides since the window is handed to the VC, not loaded by it, so any code you need to run only when the window is set, simply override the window variable and add a didSet section. Works like a charm!
Still, I may try to dig deeper to see if I can update the Window controller to load the window normally so I can get those events as designed.

How to Create a Document-Based OS X App that Launches with "Open..." Dialog

I am used to developing non-document-based applications with a single window, but now I am working on a document based application that I created using the document-based template in Xcode 5. When I run my application, it opens a new untitled document upon launch. Instead of automatically creating a new document, I would like my application to display an "Open..." dialog much like Xcode, TextEdit, and other Apple apps do. How do I go about implementing this? Is there a flag somewhere that I can set to show the dialog instead of a new document, or do I have to create an application delegate that shows the dialog upon launch? Thanks for your advice.
That would be customized behaviour.
In your application controller override applicationShouldOpenUntitledFile: to prevent opening a blank document at startup, then display the file dialog.
This is not hard but not obvious and takes a few steps to get going.
Add a window to your MainMenu.xib
Set the Visible at launch to NO in the inspector.
Now create an NSObject subclass in your project. You might include AppDelegate in the name because you will want to make it the delegate of your app.
In the interface header be sure to declare the protocol right after the word NSObject.
While there, add an IBOutlet property an NSWindow.
Back to the MainMenu.xib ...
Add an NSObject (blue cube) to your xib from the library and set its class to your new app delegate class.
Next connect your window to the property in your app delegate class and connect the window's delegate outlet to your app delegate.
Now the menu.
Locate the View menu in MainMenu and add one NSMenuItem.
Give it a title. "My fancy main window" or whatever.
Now connect it to your app delegate with both an IBOutlet (in case you want to fiddle with its enabled state or title later )
And add an IBAction for this menu item as well. Something like showMyFancyWindow:
This menu item will be persistent.
In your IBAction method call makeKeyAndOrderFront: with your app delegate's property for your window as the argument.
Extra credit
Add a BOOL property to your app delegate.
Something like showsMyFancyWindowAtLaunch
Create a constant NSString as a key above your #implementation line.
Add a checkbox button to your window.
Bind its value to your BOOL.
Add an IBAction method for the checkbox.
Inside that
[[NSUserDefaults sharedDefaults] setBool: self.showsMyFancyWindowAtLaunch forKey: theConstStringKeyYouCreated]
Then in your applicationDidFinishLaunching:
Use the corresponding bool:forKey: method of NSUserDefaults to check whether or not to call showMyFancyWindow: method at launch.
My solution, for MacOS 15 and Xcode 13, after fleshing out some of the hints above:
In your AppDelegate (or equivalent) class, turn off the creation of a New "Untitled" document using:
func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool
{
return false
}
Then, in add the following code to your applicationDidFinishLaunching() delegate method:
NSDocumentController.shared.openDocument(self)
Seems to work, though it's impossible to say whether it's a best solution or not.

Kicking off processing after opening an NSDocument

Is there a method to override that lets me perform work on my NSDocument before it displays it's window? Ideally, I'd like the option to not display the window and even close the document before it's displayed to the user. I know this sounds odd, but the document is "sometimes" more of a command file than a editable document that the user works on.
I was able to hide the window by overriding showWindows (not calling [super showWindows]) but when the window isn't visible, saving the document hangs on Lion. Also, I've tried [self close] within the readFromData but that doesn't appear to work.
The proper way to do this is to create and use your own NSDocumentController subclass.
Helpful docs: Document Opening Message Flow section of Document-Based Applications Overview and Creating a Subclass of NSDocumentController (same doc).

Getting started with cocoa for os x?

I am familiar with iOS programming, but I don't know where to put my logic when I start a Cocoa project for OS X. Are there any good online resources for transitioning to OS X from iOS?
EDIT:
Thanks for the help so far. My main question is where my code goes in the default template.
My main question is where my code goes in the default template.
Assuming a single-window app, I make a separate custom controller to own that window, and I have the application object's delegate own that controller.
So:
The application object's delegate
Owns the custom controller.
Creates it in applicationWillFinishLaunching: (note: not Did; I change this).
Releases it (and nils out the instance variable) in applicationWillTerminate:.
Does not own the primary window (I remove that ivar and all references to it).
Responds to applicationShouldTerminateAfterLastWindowClosed: with YES, so that the user can quit the application either by specifically quitting or by closing the primary window.
May also respond to application:openFile: or application:openFiles:, typically by passing the file(s) along to the controller to import or something.
The MainMenu nib
Owns the application's delegate.
Does not contain the primary window (I remove the blank one).
The primary-window controller
Owns the primary window.
Owns the model displayed in the window.
Creates initial/empty model in init.
Loads the primary-window nib with itself as owner in init.
Closes and releases window (first) and releases model (second) and subordinate controllers (third) in dealloc.
The primary-window nib
Named similarly to the controller; e.g., “SnippetListController” for the controller and “SnippetList” for the nib.
Contains the primary window.
Primary window has “visible on launch” (really on nib load) turned on and “release when closed” turned off.
The advantage of all of this is that the lifetime of every object but the application's delegate is clear and explicit, as is the area of responsibility. My application's delegate is little more than that; the only thing it does that isn't specifically an app-delegate job is owning the primary-window controller. Likewise, the primary-window controller does nothing but own the primary window and whatever model I have to show in that window. And at quit time, everything but the application's delegate gets deallocked.
For document-based apps, I stick pretty close to the template. I delete any template methods I don't need to customize, and I may switch one or both of the I/O methods to one of the other versions (e.g., from readFromData:ofType:error: to readFromURL:ofType:error:), and maybe delete the write method if I'm writing a viewer only; that's about it.
Dive in.
Seriously just jump in with both feet. Find something that you want to make and just make it.
From what you've said you've already got a fair bit of experience with Objective-C and Cocoa and most things carry straight across.

How to open a launch NSWindow in Cocoa on a button click

I have an NSWindow that I defined in interface builder. I want to make it so that when the user clicks a button, it opens a new instance that NSWindow. Do I have to subclass NSWindow or something?
If you created the window in IB and it's in your main nib file, you cannot create a "new instance" each time you press a button. When you create an object in the nib file, an instance is actually created by IB and then archived into the nib file, so you get that instance. Assuming your window is wired to a variable named auxWindow on the same object that responds to your button click, and the action message is named buttonClick, you could do something like this to show it:
-(IBAction)buttonClick:(id)sender {
if(! [auxWindow isVisible] )
[auxWindow makeKeyAndOrderFront:sender];
}
This will cause the aux window that you defined in IB to appear on the screen and become the key window (and foremost window in the application). Please note, however, that if you intend to reuse this window, you must uncheck the box in the IB Inspector that says Release on Close, otherwise you will get an access violation the next time you click the button.
This is a simple answer to your basic question, but window programming can be quite complicated and is usually very specific (for instance, do you really want a panel for what you're doing?)... so I strongly suggest that you read the Window Programming Guide for more information on this topic, and then ask very specific questions here when you get stuck.
Put the window in its own nib file, then load the nib file each time. You should use NSWindowController for loading the nib, like NSDocument does.

Resources