PDA

View Full Version : (NSBaseClass*) initFromNib(@"NibWithInherited") ?




zeppenwolf
Jun 2, 2011, 11:55 PM
I have a window which has a number of buttons which function logically like the tabs in an old style "tabs" interface, (seems like these are out of favor these days?) If button X is clicked, panel X appears; click on button Y, panel X disappears and panel Y takes its place... The panels in IB are subs of NSView, and each panel will have alot of GUI work to do, and so each will have its own subclass of NSViewController to control it.

What I'd like to do is have the window controlling code refer to "NSViewController", not "MyViewControllerSubclass".

The calling code's line that I'm stumped on is this:
MyViewConSubclass* conner =
[[MyViewConSubclass alloc] initWithNibName:@"nibName" bundle:0];

That works fine, except that I have to name each panel class. I would like it to be:
NSViewController* conner =
[[NSViewController alloc] initWithNibName:@"nibName" bundle:0];

...except that it doesn't work like that. The subclass won't be created, and in fact I haven't even allocated for MyViewConSubclass...

Is there a way for the calling code to refer to NSViewController only, but have my subclass be instantiated from the nib? Thanks.



jiminaus
Jun 3, 2011, 12:21 AM
You can't use [NSViewController alloc] and expect to magically get a subclass of NSViewController.

You can however alloc and init of a subclass of NSViewController and assign to it a NSViewController* variable.


NSViewController* conner =
[[MyViewConSubclass alloc] initWithNibName:@"nibName" bundle:0];


Will this work enough for you?

zeppenwolf
Jun 3, 2011, 11:51 PM
You can't use [NSViewController alloc] and expect to magically get a subclass of NSViewController.

I agree that when you put it that way it seems completely unreasonable. But the emphasis of my desire is not on the alloc, it's on animating the nib file.

These nib files seem to contain every possible nuance of information they could possibly need... it doesn't seem crazy to me that I should be able to "awake" a nib file containing (only) a subclass object from calling code that knows only the base class.

Since my calling code, in an NSWindow, only needs to "[[self contentView] addToSubview:awakenedObject]", why should it, (my calling code), need to know more than that the awakened object "is" an NSView ?

You can however alloc and init of a subclass of NSViewController and assign to it a NSViewController* variable.


NSViewController* conner =
[[MyViewConSubclass alloc] initWithNibName:@"nibName" bundle:0];


Will this work enough for you?

No, that's just silly-- it only throws away a cast-- the calling file would still need to know the subclass, and differentiate the init call for every panel subclass.

I did some studying at the University of Google, and I almost thought I had it for a minute, with something like this:

NSNib* theNib =
[[NSNib alloc] initWithNibNamed:@"nibName" bundle:0];
NSArray* theRay = 0;
[theNib instantiateNibWithOwner:self topLevelObjects:&theRay];


but alas it doesn't quite work. Since this is in my subclass of NSWindow, "self", the owner, has nothing to do with it really, and I have to replace self with "[MyViewConSubclass alloc]" to get that class's init function called, so it's really back to square one. ( Also there's a darn NSApplication object in there for some reason, and I can't get rid of it... )

AAARGH, but it's so close!!! Or is it? Is this the sound of one forehead beating itself against a brick wall? It's just that... Dynamic binding seems to be so central to this whole Cocoa thing, it's a bit maddening I can't do this.

jiminaus
Jun 4, 2011, 12:20 AM
No, that's just silly-- it only throws away a cast-- the calling file would still need to know the subclass, and differentiate the init call for every panel subclass.

Just making sure that it wasn't a simple problem in the guise of a complex question. ;)

So, let's embark on the journey.

I'm left with a few questions about your current setup.

How are have you set up these panel nibs? What have you setup as the file's owner? Do you have NSViewController objects in each nib?

How do you know which panels to load? Do you have something like an array of nib names?

jiminaus
Jun 4, 2011, 04:23 AM
I came up with a possible solution. If may not be an exact fit for you, but here's what I did in coding a sample project where the window doesn't need to know the classes of the panel view controllers.

I created a class which I called PanelNibFileOwner. All it has is an outlet to a view controller. The outlet is an assign property called viewController. PanelNibFileOwner's dealloc calls release on it's viewController. The only purpose of this class is to be the file owner and to receive a reference to view controller in the panel nib when it's loaded. This technique saves us from having to look for the view controller amongst the top-level objects.

Each panel is a nib called "Panel1.nib", "Panel2.nib", "Panel3.nib" etc. I'll use this naming convention to auto-discover and auto-order the panel nibs.

For each panel, I created a view nib file. I set class of the File's Owner to PanelNibFileOwner. I added a View Controller to the nib and set the class of the view controller to the specific subclass for the panel. I connected the view controller to File Owner's viewController outlet. I connected the view to the the view controller's view outlet. I also set the view controller's title in the nib. This will be used later in the nib loading code. Finally I set the autosizing properties of the view to anchor on all edges and grow in both directions.

I created a window controller for the panel window and did the panel loading and management in that, but you could equality do it an app delegate if you wanted to.

I put a popup button in the window and connected it to a panelSelector outlet in my window controller. I also put a custom view in the window and connected it to a panelContainer outlet. The panels will be shown inside this panelContainer.

I also added a currentViewController ivar to the window controller.

I added this discoverPanels method to the window controller.

- (void)loadPanels
{
[self.panelSelector removeAllItems];

NSBundle *mainBundle = [NSBundle mainBundle];

NSArray *nibURLs = [mainBundle URLsForResourcesWithExtension:@"nib" subdirectory:@""];
for (NSURL *url in nibURLs) {
NSString *lastPathComponent = [url lastPathComponent];
if ([lastPathComponent hasPrefix:@"Panel"]) {

NSLog(@"%s Loading %@", __PRETTY_FUNCTION__, lastPathComponent);

NSNib *nib = [[NSNib alloc] initWithNibNamed:lastPathComponent bundle:mainBundle];
PanelNibFileOwner *panelNibFileOwner = [[PanelNibFileOwner alloc] init];
BOOL success = [nib instantiateNibWithOwner:panelNibFileOwner topLevelObjects:nil];
if (success) {
NSViewController *viewController = [panelNibFileOwner viewController];
NSString *title = [viewController title];
[self.panelSelector addItemWithTitle:title];
NSMenuItem *menuItem = [self.panelSelector lastItem];
[menuItem setRepresentedObject:viewController];
}
else {
NSLog(@"%s Failed to instantiate %@", __PRETTY_FUNCTION__, lastPathComponent);
}
[panelNibFileOwner release];
[nib release];
}
}
}


I also added this selectPanel action, which I also connected the pop up button.

- (IBAction)selectPanel:(id)aSender
{
if (currentViewController) {
NSView *oldView = [currentViewController view];
[oldView removeFromSuperview];
}

NSMenuItem *selectedItem = [self.panelSelector selectedItem];
currentViewController = [selectedItem representedObject];
NSView *view = [currentViewController view];
[view setFrame:[self.panelContainer bounds]];
[self.panelContainer addSubview:view];
}


Note that I'm relying on NSMenuItem's representedObject to carry the view controllers loaded from the panel nibs and to associate then with the items in the pop up menu. If you don't use a pop up menu, you'll need some other technique for carrying and associating the panel view controllers.

Lastly I put this in my window controller's awakeFromNib

- (void)awakeFromNib
{
[self loadPanels];
[self selectPanel:self];
}


Hopefully you can adapt this to your needs. Note that there might be also sorts of memory leaks here, I've not checked for it.

zeppenwolf
Jun 5, 2011, 02:09 PM
OK, I got it to work. It was a whole lot of work... but most of the work was implementing a bunch of stuff which I then unimplemented because I figured out I didn't need it after all. Heh.

Ultimately, it was very simple-- in the instantiateNibWithOwner call I just set the owner to zero. I *know* I tried that before and it didn't work, so clearly my test nib was missing something crucial...


- (NSView*) panelForButton: (NSInteger) buttonID {

NSView* theView = panelsRay[ buttonID ];

if (theView)
return theView;

NSString* nibName = [NSString stringWithFormat:@"Panel%i", buttonID];

NSNib* theNib = [[NSNib alloc] initWithNibNamed:nibName bundle:0];
NSArray* theRay = 0;
[theNib instantiateNibWithOwner:0 topLevelObjects:&theRay];

int lcv = [theRay count];
if ( ! lcv )
return 0;
lcv--;

do
if ( [[theRay objectAtIndex:lcv] isKindOfClass:[NSView class]] )
return ( panelsRay[ buttonID ] = [theRay objectAtIndex:lcv] );
while (lcv--);

return 0;
}

( Are tabs in code tags set to 8 here? Seems a bit excessive... )

Each nib file has only one NSView, so I can just walk over the objects returned from instantiate() until I find it.

Anyway, it works! I'm not sure why I didn't need a kind of device you outlined up there jiminaus, but thanks.

jiminaus
Jun 5, 2011, 05:50 PM
( Are tabs in code tags set to 8 here? Seems a bit excessive... )


One of the pitfalls of using tabs in your code, I guess.


Each nib file has only one NSView, so I can just walk over the objects returned from instantiate() until I find it.


Where do the view controllers come into your setup?

zeppenwolf
Jun 6, 2011, 12:43 AM
One of the pitfalls of using tabs in your code, I guess.
Ah yes, indeed, it is one of the pitfalls. But remind me again, what was the other pitfall? :)

Where do the view controllers come into your setup?

The window is basically a preferences dialog, and there are oodles of prefs. The entire thing is done in custom graphics, btw, so in its naked state the window is just an image with five "buttons" (that act like "tabs") down one side and a large empty space for panels to be displayed, which are also custom graphics that merge seamlessly with the background image.

When a button is clicked, the corresponding panel is displayed, and the panel contains probably a bunch of controls.

So each panel lives in a nib, together with a subclass of viewController which will handle all the actions the controls generate, ensure "radio-ness" for the controls that look like radio buttons, etc.

In the code above, the calling code in the window only needs a pointer to the panel/view; once that nib is loaded, and the NSImageView inside it is located, the pointer is stored for next time it is needed. But I don't need a pointer to the viewController, at least so far.

Did I answer the question?

jiminaus
Jun 6, 2011, 12:57 AM
Ah yes, indeed, it is one of the pitfalls. But remind me again, what was the other pitfall? :)


(Rant swollowed)



But I don't need a pointer to the viewController, at least so far.


Except in a non-GC environment you'll definitely be leaking the view controllers. In a GC environment, the view controllers will be garbage collected prematurely.

zeppenwolf
Jun 8, 2011, 01:06 AM
Except in a non-GC environment you'll definitely be leaking the view controllers. In a GC environment, the view controllers will be garbage collected prematurely.

Ok, thanks for bringing this to my attention-- I admit this issue hadn't even crossed my mind...

About "leaking", well that's a non-issue-- it's not a "leak" if it's my conscious decision to not throw away work I've done just because the window is "closed", read "hidden"...

But I don't want the GC to borg my objects up, so I've been running tests and printing retain counts out, and I have to say that I'm now utterly confused. I have no idea what's happening-- it seems that my viewConr's have a retain count of one while they being inited, but then later they're zero, then they're back to one... ( And the NSImageViews, as soon as they come out of the nib, they're at four!! Wha...?!? )

Thanks for alerting me to this-- is there a rule of thumb? Should I override an appropriate init() for each viewCon'r and just add a [self retain] to it?

jiminaus
Jun 8, 2011, 01:57 AM
Ok, thanks for bringing this to my attention-- I admit this issue hadn't even crossed my mind...

About "leaking", well that's a non-issue-- it's not a "leak" if it's my conscious decision to not throw away work I've done just because the window is "closed", read "hidden"...

But I don't want the GC to borg my objects up, so I've been running tests and printing retain counts out, and I have to say that I'm now utterly confused. I have no idea what's happening-- it seems that my viewConr's have a retain count of one while they being inited, but then later they're zero, then they're back to one... ( And the NSImageViews, as soon as they come out of the nib, they're at four!! Wha...?!? )

Thanks for alerting me to this-- is there a rule of thumb? Should I override an appropriate init() for each viewCon'r and just add a [self retain] to it?

Analyzing retain counts is not a good idea. As you can see the results can be baffling. This is because there are internal, transitory retains in Cocoa library code. It's better to use Instruments to analyze your object lifetimes. See the Instruments User Guide (http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/InstrumentsUserGuide) and the Memory Usage Performance Guidelines (http://developer.apple.com/library/mac/#documentation/Performance/Conceptual/ManagingMemory).

Don't abuse memory management. I can tell you from experience, it will come back to bite you. Doing something like calling retain inside init is really smelly. Just follow the memory management rules as set out in the Memory Management Programming Guide (http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt).

In a GC environment, retain and release are no-ops. You must maintain a least one strong reference to every object you want kept alive.