What's the best way to switch between multiple styles of main windows in cocoa?
I mean something like iTunes with its mini-player view and its normal sized view.
I want to layout different nib-files and let the user choose between the small one or the bigger one with more details.
Do I need a NSWindowController or is it ok to load nibs in the App Delegate like this:
[NSBundle loadNibNamed:#"BigWindow" owner:self];
You should always use NSWindowController to load a nib containing a window. This is because NSWindowController handles the nib memory management for you, which is otherwise somewhat painful.
If you just want to load a view from a nib use NSViewController instead.
Related
I'm new to the NSDocument architecture and am looking to set up multiple windows (and hence multiple NSWindowController objects) for a single document.
From what I understand, NSDocument was really created to work with a single window, and it seems that the ability to have multiple windows was shoehorned in later. For example, it seems that the NSDocument should always be the file's owner for any window's NIB files. But what if I wanted to separate the window controllers from the document?
For example, in the NSDocument subclass I am currently using the code:
- (void)makeWindowControllers {
[self setMyWindowController1:[[WindowControllerType1 alloc] initWithWindowNibName:#"MyWindow" owner:self]];
[self addWindowController:[self MyWindowController1]];
}
But the NIB file "MyWindow"'s file owner is set to the NSWindowController subclass (WindowControllerType1), NOT my NSDocument subclass. In this case, whenever I look to get the document by using [[NSDocumentController sharedDocumentController] currentDocument], this ALWAYS returns nil.
I figure this can be rectified if I set the NIB file's owner to the NSDocument subclass, but then all of my outlet links break, and I'm not sure how to link to the NSWindowController subclass (WindowControllerType1), as the typical course of action (as far as I can tell) is to make the NSDocument a window controller delegate as well, which I would like to avoid!
Any suggestions?
EDIT:
Let me clarify and add some new information. I am aware of Apple's position on using the WindowController's document property. However, as I plan of having a larger number of nested NSViews in each window, I want to avoid passing the document through a large chain of views in order to accomplish this.
The issue is not necessarily this chain. It is mostly that when the [[NSDocumentController sharedDocumentController] currentDocument] is ALWAYS nil, none of the "for free" features of NSDocument seem to work, such as undo/redo. This is the major issue that I need to resolve.
From what I understand, NSDocument was really created to work with a single window, and it seems that the ability to have multiple windows was shoehorned in later.
No, makeWindowControllers is available in OS X v10.0 and later.
But what if I wanted to separate the window controllers from the document?
The window controller owns the NIB.
Any suggestions?
Do
[self setMyWindowController1:[[WindowControllerType1 alloc] initWithWindowNibName:#"MyWindow"]].
NSWindowController has a property document which is set by addWindowController:.
Use document property of NSWindowController instead of currentDocument.
In real apps the view hierarchy can be complex at it really helps to be able to put different views in different nibs. I am following InfoBarStackView example project they give a really nice example of how to use the new NSStackView class which hosts different views. They make a DisclosureViewController which is responsible for hosting a content view
changing the it's size so that is can go from a open to closed state.
Here is a simplified example. What we have are two separate nibs:
DisclosureViewController
ContentViewController
What is the simplest way to load the content view inside the placeholder view of the disclosure view? Is it possible to do this only in IB only?
Currently my AppDelegate has a lot of redundancy because it need to hold references to both view controllers. I wondering if there is a way of simplifying the situation? For this simple example, the AppDelegate would load from the two different nibs using code like this,
// In AppDelegate.m
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[_disclosureView1.view replaceSubview:_disclosureView1.placeholder with:_contentView1.view];
[(NSView*)_window.contentView addSubview:_disclosureView1.view];
}
You can have NSViewControllers in your main XIB corresponding the the views you want in your NSStackView — in XIB’s inspector you can set the name of the other XIBs they should load to get their ‘view’s.
Assuming you had IBOutlets onto these viewControllers in your main XIB, it’d be as easy as calling:
/* load the views into the stack view */
_stackView = [NSStackView stackViewWithViews:#[_viewControllerOutlet1.view, _viewControllerOutlet2.view, _viewControllerOutlet3.view]];
How can I load a nib inside of another window?
I tried initWithWindowName,
if (mmController == NULL)
mmController = [[mainMenu alloc] initWithWindowNibName:#"mainMenu"];
[mmController showWindow:self];
but it opens a new window.
I also tried loadNibNamed
[NSBundle loadNibNamed:#"mainGame" owner:self];
and it succeeded, but when I try to use the same method to get back to the main menu,
[NSBundle loadNibNamed:#"mainMenu" owner:self];
it doesn't work. It does nothing at all...
Any ideas?
I tried initWithWindowName,
You mean initWithWindow¹Nib²Name³:, which takes the name (3) of a nib (2) containing a window (1).
if (mmController == NULL)
This should be nil, not NULL, since you are comparing an Objective-C object pointer.
mmController = [[mainMenu alloc] initWithWindowNibName:#"mainMenu"];
What is mainMenu here? It must be a class, but what is it a subclass of?
[mmController showWindow:self];
From this message and the previous message, I'm guessing mainMenu is a subclass of NSWindowController.
Guessing should not be required. You should name your classes specifically, so that anybody can tell what the class is and its instances are merely by the class name.
Brevity is a virtue, but if you need to go long, go long. We've got modern tools with name completion. The tab key can eliminate the sole advantage of an abbreviated name.
but it opens a new window.
Yes. You created a window by loading it from a nib, and then you told the window controller to show that window. Showing a new window is the expected result.
I also tried loadNibNamed
[NSBundle loadNibNamed:#"mainGame" owner:self];
and it succeeded, but when I try to use the same method to get back to the main menu,
There is no “get back”. Loading a nib is simply creating objects by loading them from an archive. You can load the same nib multiple times, and loading a nib does not somehow undo the results of loading a previous nib.
You may want to read the Resource Programming Guide, which covers nibs as well as image and sound files, and the Bundle Programming Guide.
If you want to hide the window you loaded from the mainGame nib, do that. The term for this in AppKit is “ordering out” (as opposed to “ordering in”, which “ordering front” and “ordering back” are specific ways of doing).
[NSBundle loadNibNamed:#"mainMenu" owner:self];
it doesn't work. It does nothing at all...
Are you trying to load the MainMenu nib that came with your project? If so, make sure you get the case right—you don't want your app to be broken for people who run it from a case-sensitive volume, nor do you want it to be broken for people who use the default case-insensitive file-system.
If that's not what you're trying to do, then it isn't clear what you are trying to do. MainMenu is normally the nib containing the main menu (the contents of the menu bar); naming any other nib “mainMenu” or anything like that is going to cause confusion at best and problems at worst. If this is supposed to be some other nib, you should give it a different name.
Either way, this is not what you need to do. If you want to hide the window you loaded from mainGame, then you need to hide that window, not load a different nib.
Moreover, once the window is loaded, do not load it again (unless you close and release it). Once you have loaded it, you can simply order it back in. Most probably, you will want to both make it key and order it front.
On the Mac, you are not limited to one window at a time; indeed, your app has multiple windows (at least three), no matter what you do. The APIs are built around your ability to show multiple windows.
See the Window Programming Guide for more information.
How can I load a nib inside of another window?
As Justin Meiners already told you, you may want NSViewController for that, although you can go without and just load the nib containing the view directly using loadNibNamed:.
Be warned that NSViewController is not nearly as powerful/featureful as Cocoa Touch's UIViewController.
You'll want to read the View Programming Guide for this.
When I create a new UIViewController in xcode, it offers to make me an associated nib for the interface. However, when I create a UIView, it does not. If my understanding of MVC is correct, views should really be the parts that contain the interface elements (i.e. the nib) while view controllers are the parts that hook the functionality to the views they control.
I'm sure I'll be able to get it working together either way, so this is more of an exploratory question.
This seems like a case where I'm missing some fundamental understanding of how these two parts should be used. What am I missing?
Because UIView is usually not used in such way.
However How do I associate a nib (.xib) file with a UIView?
The answer I eventually got that satisfied my interest was roughly this:
The job of a view controller is to manage a view hierarchy. Interface Builder is an excellent tool for creating view hierarchies. Xcode offers to create a .xib when you create a new view controller because chances are high that you'll want to load the controllers' views from a .xib.
.xib files aren't necessarily meant to hold every UIView (or subclass) that goes into the view, just a general outline of views that don't change during the life of the view. The other UIViews are much easier to create programmatically since they change often.
I had a similar confusion. Basically (according to the MVC) the View is contained inside the Controller. In the iPhone this means that UIViewController has a property called 'view' which is a UIView instance.
A XIB file (and this is not mentioned often) is a serialised UIView instance. It is roughly an XML sub format which represents a UIView with all its subsequent views. So when you create a UIViewController, a UIView is created in the form of a XIB and bounded to that controller.
A new UIView therefore does not have a XIB because they are essentially the same thing...
I have used NSWindowController in projects several times, and feel like I have a (very)rough grasp of the concepts behind this important class. What I would like to do with this post is to clarify/correct my own understandings, and hopefully help other learners get that first step into understanding. It's the at-a-glance concepts, overview, and best practices that I find is most useful, and often lacking in the documentation. Here is my take on NSWindowController (questions are interspersed in bold):
An NSWindowController (NSWC) subclass exists (conceptually) just beneath every window nib, acting as the glue between the user interface elements and the model objects that they control/represent. Basically, every window in your application should have its own NSWC subclass.
The File's Owner of the nib should always be the NSWC subclass. Is this the case even for the MainMenu.xib application?
The NSWC window property should always be linked to the NSWindow in InterfaceBuilder.
You should override the 'init' method, using [super initWithWindowNibName:], so that when you refer to [mycontroller window] it will load the nib. Should this also be the case for the NSWC for the MainMenu.xib window, even though this is opened at startup?
The NSWC shouldn't do too much heavy lifting - it should simply pass messages to instances of objects, and present those objects in the UI.
It can modify the UI using binding, or acting as a delegate for tables etc., or by actively changing the UI elements when it observes a change, or a combo of any of the above (which one you use seems to be a matter of taste, with pros and cons on all sides).
An NSWC can create instances of other NSWCs when necessary (for example, when opening a one-off sub-window).
Use the [mycontroller showWindow:nil] to display the associated window at the front. If you want the window to appear as a sheet, use something like:
NSWindowController* mycontroller = [[MyController alloc] init];
[NSApp beginSheet: [mycontroller window]
modalForWindow: [self window]
modalDelegate: self
didEndSelector: #selector(didEndMySheet:returnCode:contextInfo:)
contextInfo: nil];
The didEndSelector: should be a method of the NSWC of the parent window, and can access and release 'mycontroller' with [sheet windowController].
- To close the window call the performClose: method of NSWC's window.
Some Questions:
Should the NSWC of the MainMenu window also be the application delegate, or should this be a different class?
In the same vein, should the main NSWC handle files (drag/drop and opening), or should it be passed on to the app delegate, or is that just a matter of taste?
Please correct me if any of this is bad practice, or is just plain wrong. I am looking to clarify my understanding of NSWindowController, so any additions (in the form of best practices, experiences, gotchas) would be highly appreciated.
Thanks,
Laurie
What are window controllers actually for?
Window controllers are tools to load a window from a NIB file and for managing the memory of the resources allocated in the NIB. Before there where NSWindowControllers one basically had to write the same code for every window or invent an own window controller class.
Of course they are also controllers in the Model/View/Controller sense, so they are the right place to connect the views from the window to the model objects. To do this they often need to act as the delegate or data source for a view object. So you got this part perfectly right.
Also window controllers are a tool for code reuse. It makes it easy to drop the window controller class and it’s XIB/NIB into another project and use it there.
So yes, every window from a NIB should be owned by a window controller, with one exception. Actually, this is just a guideline for good code, nothing enforces it.
WindowControllers and MainMenu.xib
MainMenu.xib is a different thing, there you can’t use a window controller. This NIB gets loaded by NSApplication so this has to be it’s "Files owner". There is no way to get a window controller between the NSApplication and the NIB. It also isn’t necessary to use a window controller for memory management there, since the application object lives for the entire runtime of the program, so it doesn’t have to clean up it’s resources from the NIB when it gets deallocated.
If you really need a window controller for your main window you cannot put this in the MainMenu.xib.
I hope this helps. There probably is a lot more to say about window controllers too
Is this the case even for the MainMenu.xib application?
No, the MainMenu nib is owned by NSApplication (that's who loads it).
Should this also be the case for the NSWC for the MainMenu.xib window, even though this is opened at startup?
No, NSApplication loads the main nib based on your applications file's "NSMainNibFile" property. (It just happens to be pre-set to "MainMenu" in the template Xcode projects.) If you want to change its name then change it there (and rename your nib file). (BTW: This property can also be changed in your target's "Summary" view in Xcode 4.)
Should the NSWC of the MainMenu window also be the application delegate, or should this be a different class?
The owner of the NSMainNibFile nib is the instance of NSApplication that loads it and by association any delegate of that instance. Neither of these are NSWC sub-classes.
In the same vein, should the main NSWC handle files (drag/drop and opening), or should it be passed on to the app delegate, or is that just a matter of taste?
There is no "main NSWC" (The app/app-delegate is the controller for the NSMainNibFile).
All drag-n-drop operations are handled by NSWindow or NSView sub-classes. I usually use a special NSWindow or NSView sub-class that just passes all drag-n-drop methods thru to the delegate. For example:
- (unsigned int) draggingEntered:sender
{
return [[self delegate] draggingEntered:sender];
}
This way I can keep all my window/view code together in their respective controller (as determined by their nib owner). And because the window/view specific code is in the controller (not the NSWindow/NSView subclass) different types of NSWindows/NSViews can all use the same drag-n-drop sub-classes.