When creating a nib, I have 2 types to create, a WindowNib or a ViewNib. I see the difference is, the window nib has a window, and a view.
How do I load a view nib into another window? Do I have to create a WindowController and add a window then load the nib in the window?
Typically, you should have a Controller for each NIB. So in the case of a Window NIB you would have an NSWindowController subclass as the File's Owner. Similarly for a View NIB you would have an NSViewController subclass as the File's Owner. In the case you present the NSWindowController subclass would instantiate the NSViewController subclass (passing it the appropriate View NIB) and then attach the NSViewController's view property to the window's view hierarchy.
Example in your Window Controller's awakFromNib method you would have the following:
- (void) awakeFromNib {
_viewController = [[MyViewController alloc] initWithNibName: #"MyView" bundle: nil];
[[[self window] contentView] addSubview: [_viewController view]];
}
You could also place this code in the windowDidLoad method of your NSWindowController subclass.
Related
Apple's resource programming guide (RPG) states "it is better to distribute components across multiple nib files."...
therefore,
i have an associate window nib (Nib 2) that has an nsobjectcontroller that needs to be linked (selection self) to a nsarraycontroller in the main document window nib (Nib 1).
i need to share a common instance (either the nsarraycontroller in nib 1 or nsobjectcontroller in nib2). I can add a custom object in Nib 1. and set the File's Owner to that type of custom object. however, each nib instantiates their own instance.
is there a method of setting which nib an object was instantiated, or declaring an external reference.
i also "Make the File’s Owner the single point-of-contact for anything outside of the nib file" (RPG). Which is a NSWindowController.
Thanks in advance.
You probably want to make the owner of NIB1 responsible for instantiating NIB2. This will allow it to be the owner of both NIBs. In the common case, it might look something like this:
// In the interface...
#property (nonatomic, readwrite, retain) NSArray* nib2TopLevelObjects;
// In the implementation...
- (void)awakeFromNib
{
NSNib* nib2 = [[[NSNib alloc] initWithNibNamed: #"NIB2" bundle: [NSBundle mainBundle]] autorelease];
NSArray* tlo = nil;
[nib2 instantiateWithOwner: self topLevelObjects: &tlo];
self.nib2TopLevelObjects = [tlo retain];
// Do other stuff...
}
- (void)dealloc
{
[_nib2TopLevelObjects release];
[super dealloc];
}
At the end of this, NIB2 will have been instantiated with NIB1's owner as it's owner as well, and NIB2 will have plugged its objects into the shared owner (be sure not to plug things into the same outlet from both NIBs.)
All that said, I'm not sure this is necessarily the right pattern to use here. If these windows are both views upon the same document, you should probably make an NSWindowController subclass for each window and override -[NSDocument makeWindowControllers] to instantiate them. (The NSWindowController will be the "File's Owner" for each NIB.) Having the document NIB's owner be the NSDocument subclass is a "short cut" for simple situations. Once you need multiple windows, NSWindowControllers are the way to go.
Each NSWindowController can get back to the document via -document and the NSDocument subclass can coordinate state between the different NSWindowControllers. This is a cleaner approach, and avoids all the shenanigans with clobbered IBOutlets, etc.
For your specific case, I could see having a property like sharedArrayController on the NSDocument subclass that gets the NSArrayController from NIB1 during -makeWindowControllers, and re-vends it. Then you can then access it from NIB2 by binding to File's Owner > document.sharedArrayController.selection.
I have a NSWindow with an NSView and an NSTextField inside.
I'm using Interface builder right now. I have dropped the two controls on the default NSWindow and subclassed an NSView. I'm implementing the -drawRect method from NSView and I need to access to the content of NSTextField.
How do I refer to the instance of NSTextField from a method inside the NSView ?
Your NSWindow is (or should be) controlled by a window controller. In IB you create an outlet for the NSTextField in your window controller. Using the outlet, you can then refer to the NSTextField:
In your window controller .h file:
#property (strong) IBOutlet NSTextField *myTextField;
In your window controller .m file:
#synthesize myTextField;
From there you can in your controller:
[[self myTextField] setEditable: NO];
A point to note is that you do not access the controls in a window directly from that window as windows (and all Cocoa controls for that matter) are statically stored in a XIB/NIB file. All access to controls (UI elements) is channelled through controllers (NSWindowController, NSViewController) which in turn are capable of loading XIB/NIB files.
Apple provides various samples in their docs on how to do this.
I created a NSWindow xib file that I want to open on click of a button in another window.
Now, to control the behavior of the NSWindow, I dragged an object from Library in xib and changed it to subclass of NSWindowController (i.e. ListingWindowController) that I defined in XCode.
Similarly I also created a subclass of NSViewController (i.e. ListingViewController) to manage the NSView inside the NSWindow. To do this, I dragged NSViewController from Library in xib and changed its class to ListingViewController.
#class ListingViewController;
#interface ListingWindowController : NSWindowController {
IBOutlet ListingViewController *listingVC;
}
#property (nonatomic, retain) IBOutlet ListingViewController *listingVC;
#end
I connected window and listingVC of my window controller in IB.
Now to invoke this window on click of a button in my launch (first) window, I create the window controller using initWithWindowNibName like this..
- (IBAction) pushConnect:(id)sender {
NSLog(#"Connect pushed.");
if (wc == nil) {
wc = [[ListingWindowController alloc] initWithWindowNibName:#"ListingWindow" owner:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(closeWindow:) name:NSWindowWillCloseNotification object:nil];
[wc showWindow:sender];
}
}
The problem is that despite all the bindings done in IB for the view controllers of upcoming window/view, the window and listingVC comes out to be (null), (null) even after the new window has loaded (below code).
- (void)windowDidLoad {
[super windowDidLoad];
NSLog(#"windowDidLoad = %#, %#", self.window, self.listingVC);
}
Please help why the connections are not working. I'm banging my head against this problem for quite a while now.
PS: I'm coming from iOS programming background. So, I'm assuming the Mac's window/view controller behave similar to iOS UIViewControllers.
TIA..
Note that:
wc = [[ListingWindowController alloc] initWithWindowNibName:#"ListingWindow" owner:self];
means that self (it’s not clear what self is from your question) is the owner of ListingWindow.nib. This means that self is the one who keeps outlets to objects in that nib file, and self is responsible for releasing the top-level objects in the nib file. This also means that you’re creating an instance of ListingWindowController in your code and another instance inside your nib file since you’ve dragged an object of class ListingWindowController onto the nib file.
This is not how it’s supposed to be.
In the vast majority of cases, a window (view) controller loads a nib file and becomes its owner. It has a window (view) outlet that must be linked to a top-level window (view) in the nib file. Being the nib file’s owner, it must have been created before the nib file is loaded.
In order to achieve this for your window controller, you need to set the file’s owner class to ListingWindowController. You must not drag an object cube and instantiate the window controller inside the nib file. The window controller is the owner of the nib file, so it must exist before the nib file is loaded. You must also link the window outlet in file’s owner to the top-level window object in the nib file so that the window controller is aware of what window it should manage.
Having done that, use:
wc = [[ListingWindowController alloc] initWithWindowNibName:#"ListingWindow"];
instead of:
wc = [[ListingWindowController alloc] initWithWindowNibName:#"ListingWindow" owner:self];
since wc is supposed to be the owner of the nib file.
View controllers work similarly. They’re created before loading the nib file, are responsible for loading a nib file that contains a view as a top-level object, are that nib file’s owner, and have a view outlet that must be linked to that top-level view.
It’s not clear from your question whether you have a separate nib file for the view. If you don’t, then using a subclass of NSViewController is not needed at all — you could use a subclass of NSObject instead. If you insist on using NSViewController to manage a view that’s not loaded from a separate nib file, then you should override -loadView so that you get a reference to the view by some means other than loading it from a nib file, and sending it -setView: so that it is aware of the view it’s supposed to be managing.
Recommended reading: Nib Files in the Resource Programming Guide, NSWindowController class reference, NSViewController class reference.
I have to upgrade one existing application and neeed to split its existing UI into separate NIBs. I have planning to start with creating separate NIBs and NSViewController for all my splitted UIs. Now the problem is my NSViewController didn't respond on keyboard TAB and SHFIT+TAB events, I simply wants my NSViewController to set focus on appropriate child control in my dynamically loaded NSViewController view when user click TAB or SHIFT+TAB.
Thanks,
EDITED :
Following is my requirement.
I have three sub views which i need to load dynamically and switch using NSPopupButton in my MainWindow placeholder NSBox.
For checking i have created new cocoa app and added one NSPopupButton and NSBox to Window and join the outlets for NSPopupButton and NSBox.
Second, I have created three new NSViewController's with three different NIB's containing separate custom view containing two or three NSTextField's child controls.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
}
In the main app delegate function i am adding all the three NSViewController's to an array and later swapping of views using replaceSubview to replace views in placeholder NSBox.
I have added following code in all the three NSViewController's, but i am still not getting focus on child controls by pressing TAB or SHIFT+tab keys.
- (void)loadView {
[super loadView];
// store the responder that’s right after the view in the responder chain
NSResponder *nextResponder = [[self view] nextResponder];
// set the view controller (self) as the next responder after the view
[[self view] setNextResponder:self];
// set the stored responder as the next responder after the view controller
[self setNextResponder:nextResponder];
}
Even though NSViewController inherits from NSResponder, Cocoa doesn’t automatically add NSViewController instances to the responder chain. You need to do it yourself.
One possible solution is to insert your view controller into the responder chain between the view it is controlling and the next responder of that view. For instance, in your view controller implementation,
- (void)loadView {
[super loadView];
// store the responder that’s right after the view in the responder chain
NSResponder *nextResponder = [[self view] nextResponder];
// set the view controller (self) as the next responder after the view
[[self view] setNextResponder:self];
// set the stored responder as the next responder after the view controller
[self setNextResponder:nextResponder];
}
I'm having a difficult time wrapping my head around loading views with Interface Builder and NSViewController.
My goal is to have a view which meets the following description: Top bar at the top (like a toolbar but not exactly) which spans the entire width of the view, and a second "content view" below. This composite view is owned by my NSViewController subclass.
It made sense to use Interface Builder for this. I have created a view nib, and added to it two subviews, laid them out properly (with the top bar and the content view). I've set File's Owner to be MyViewController, and connected outlets and such.
The views I wish to load in (the bar and the content) are also in their own nibs (this might be what's tripping me up) and those nibs have their Custom Class set to the respective NSView subclass where applicable. I'm not sure what to set as their File's Owner (I'm guessing MyController as it should be their owner).
Alas, when I init an instance of MyViewController none of my nibs actually display. I've added it to my Window's contentView properly (I've checked otherwise), and actually, things sort of load. That is, awakeFromNib gets sent to the bar view, but it does not display in the window. I think I've definitely got some wires crossed somewhere. Perhaps someone could lend a hand to relieve some of my frustration?
EDIT some code to show what I'm doing
The controller is loaded when my application finishes launching, from the app delegate:
MyController *controller = [[MyController alloc] initWithNibName:#"MyController" bundle:nil];
[window setContentView:[controller view]];
And then in my initWithNibName I don't do anything but call to super for now.
When breaking out each view into its own nib and using NSViewController, the typical way of handling things is to create an NSViewController subclass for each of your nibs. The File's Owner for each respective nib file would then be set to that NSViewController subclass, and you would hook up the view outlet to your custom view in the nib. Then, in the view controller that controls the main window content view, you instantiate an instance of each NSViewController subclass, then add that controller's view to your window.
A quick bit of code - in this code, I'm calling the main content view controller MainViewController, the controller for the "toolbar" is TopViewController, and the rest of the content is ContentViewController
//MainViewController.h
#interface MainViewController : NSViewController
{
//These would just be custom views included in the main nib file that serve
//as placeholders for where to insert the views coming from other nibs
IBOutlet NSView* topView;
IBOutlet NSView* contentView;
TopViewController* topViewController;
ContentViewController* contentViewController;
}
#end
//MainViewController.m
#implementation MainViewController
//loadView is declared in NSViewController, but awakeFromNib would work also
//this is preferred to doing things in initWithNibName:bundle: because
//views are loaded lazily, so you don't need to go loading the other nibs
//until your own nib has actually been loaded.
- (void)loadView
{
[super loadView];
topViewController = [[TopViewController alloc] initWithNibName:#"TopView" bundle:nil];
[[topViewController view] setFrame:[topView frame]];
[[self view] replaceSubview:topView with:[topViewController view]];
contentViewController = [[ContentViewController alloc] initWithNibName:#"ContentView" bundle:nil];
[[contentViewController view] setFrame:[contentView frame]];
[[self view] replaceSubview:contentView with:[contentViewController view]];
}
#end
Should not MainViewController be a subclass of NSWindowController? And the outlets in the class connected to view elements in the main Window in MainMenu.xib?
Let's hope old threads are still read...