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.
Related
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.
I create a cocoa application on OSX 10.8. I create a windows derived from NSWindowController and also create a .xib file for this window, I want to show this window when application start, so I add the following in the applicationDidFinishLaunching function
wndAgreement = [[AgreementWindow alloc] initWithWindowNibName:#"AgreementWindow"];
[wndAgreement showWindow:self];
But I still see the application's main window showing. Basically, the main window is behind my window. How to make the main window not show at all and only showing my window? The reason I am doing this is because I am building a wizard application, so click next on one window will open another window and close the current window.
The main window is probably showing because it's in the main menu NIB and has Visible At Launch enabled. "Visible At Launch" really means "visible when the NIB is loaded".
The quickest fix is to turn off Visible At Launch for that window. Better would be to remove that window from that NIB. Although it's the default behavior you get from Apple's project templates, it's a bad idea of have a window in your main menu NIB. They should all be done like your new window, with a separate NIB.
Go to the "Supporting Files" Folder of your project and click on the file that ends in "info.plist." Under "Main nib file base name," you will likely see "MainMenu." Delete this and enter the name of the window you want to show at launch.
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.
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.
The problem I've got is that when the preferences window is opened and then closed, it will not open again. Why is this happening and how can this be fixed?
EDIT: Just noticed also I've got the same problem with Main Window.
The window is being opened via the menu bar and is in a separate NIB file.
It sounds like you forgot to set the 'window' outlet of your window controller (in the Nib, the File's Owner) to point to your window. Once you connect its 'window' outlet the window controller's showWindow: method will work.
Hard to say without seeing the code, but one possibility is that you have "Release When Closed" checked for the window in Interface Builder, and you are loading it once, caching it, then expecting to be able to close and re-open it without loading from the nib again.
For any other noobs like me, having this issue :)
Don't forget to set the file's owner class to be the same as the controller class, then you can connect the 'window' outlet to the panel.
I encountered this same problem while working on the chapter 12 (Nib Files and NSWindowController) exercise in Aaron Hillegass's Cocoa Programming for Mac OS X book. Doug's answer above was the solution - I hadn't linked the window outlet of the Preferences.xib's file owner to the window itself (in this case the Panel (Preferences) window).
I right clicked on the "File's Owner" in the Preferences.xib file then left-mouse-button dragged from the Window outlet to the Panel (Preferences). Once done and rebuilt the application worked as intended. I could close and reopen the custom preference panel and my previous settings were still there (since the preference window is not unloaded just hidden).