Question

I have looked at other SO questions, but cannot see the help I need: I have a Cocoa app with NSViewControllers subclassed in an NSTabView.

Everything works except for - when I first run the app, Tab 0 contents do not show. When I select tab 1, then tab 0, Tab 1 shows, and Tab 0 shows ok.

I need some advice on how to initialize the tab view, as I guess I'm doing something wonky.

This is what I'm doing so far:

    - (void)windowDidLoad
    {

        NSLog(@"%s", __FUNCTION__);
        NSViewController *newController = nil;
        newController = [[FirstViewController alloc]
                         initWithNibName:@"FirstViewController" bundle:nil];
        NSTabViewItem *item;
        [[self aTabView] selectFirstTabViewItem:newController];
        newController.view.frame = aTabView.contentRect;
        [item setView:firstViewController.view];
        self.currentViewController = newController;
    }

UPDATE, with great comments from Peter Hosey, I have working code as follows:

- (void)windowDidLoad
{

    NSLog(@"%s", __FUNCTION__);

    firstViewController = [[FirstViewController alloc]
                     initWithNibName:@"FirstViewController" bundle:nil];

    secondViewController = [[SecondViewController alloc]
                                       initWithNibName:@"SecondViewController" bundle:nil];
    thirdViewController = [[ThirdViewController alloc]
                                        initWithNibName:@"ThirdViewController" bundle:nil];

    NSTabViewItem *item;
    if (firstViewController != nil) {
        item = [aTabView tabViewItemAtIndex:0];
        [item setView:[[self firstViewController] view]];
        //[firstViewController.view.frame = aTabView.contentRect];
        self.currentViewController = firstViewController;
    }
}




- (BOOL)switchViewController:(NSTabView*)tabView item:(NSTabViewItem*)nextItem {
    NSLog(@"%s", __FUNCTION__);
    NSViewController *newController = nil;
    newController.view = NO;
    // assume a different identifier has been assigned to each tab view item in IB
     itemIndex = [tabView indexOfTabViewItemWithIdentifier:[nextItem identifier]];
    switch (itemIndex) {
        case 0:
            newController = firstViewController;
            break;
        case 1:
            newController = secondViewController;
            break;
        case 2:
            newController = thirdViewController;
            break;
    }
    if (newController != nil) {
        [nextItem setView:newController.view];
         newController.view.frame = tabView.contentRect;
        self.currentViewController = newController; 

        /*
         NSLog(@"%s: myTabView.contentRect=%@ currentViewController.view.frame=%@",
              __FUNCTION__, NSStringFromRect(aTabView.contentRect),
              NSStringFromRect(currentViewController.view.frame));
         */
        return YES;
    }
    else {
        // report error to user here
        NSLog(@"Can't load view for tab %ld", (long)itemIndex);
        return NO;
    }
}

- (BOOL)tabView:(NSTabView*)tabView shouldSelectTabViewItem:(NSTabViewItem*)tabViewItem {
    return [self switchViewController:tabView item:tabViewItem];
}
Was it helpful?

Solution

    NSViewController *newController = nil;
    newController = [[FirstViewController alloc]
                     initWithNibName:@"FirstViewController" bundle:nil];

This declaration and statement can be consolidated into a single declaration-with-initializer.

Doing this in both your init and windowDidLoad methods does not make sense; you create two separate FirstViewControllers.

    NSTabViewItem *item;

At no point do you ever create an NSTabViewItem and assign it to this variable. If you were expecting your selectFirstTabViewItem: message to do that, (1) no, that's not what that does and (2) there is no way that it could possibly do that because it doesn't know about your variable or even that you have one.

Under ARC, this variable is implicitly initialized to nil, so all subsequent messages that attempt to use an item here will simply do nothing. If you are not using ARC, failure to initialize this variable will cause a (possibly intermittent) crash when you try to use it.

     [[self aTabView] selectFirstTabViewItem:newController];

Passing your shiny new view controller (either one of them) here does not do what I think you think it does.

selectFirstTabViewItem: is an action method; its argument is the sender of the message, which should generally be a control or menu item. When you set it as the action of a button, menu item, etc., that button/menu item/whatever will be the sender.

Passing a view controller as the sender does not make sense. Moreover, selectFirstTabViewItem: does not change anything about the first tab view item (if there even is one), and it definitely does not assume or check that its sender is a VC and if so set the item's view to the VC's view. It selects the first tab view item (makes it the current tab), nothing more.

Most probably, this method ignores its argument. If you want to set the first tab view item's view to the VC's view, you must do that yourself. You will, of course, have to get (or create) the tab view item first.

    [item setView:firstViewController.view];

Under ARC, this does nothing; under MRC, it may crash (see above).

What you need to do

I assume that this code is from an NSWindowController subclass. I further assume that this WC has a nib.

  • Make sure that aTabView is an outlet and that is connected in the WC's nib. aTabView should be a simple property that is declared with the IBOutlet keyword. Don't implement any custom accessor methods for it; just declare the property, hook it up in the nib, and access it in your code.
  • Create your view controller(s) in exactly one place, and make sure you only do it once (not in or from both initWithWindow: and windowDidLoad). You can create all of the VCs in your window controller's nib. Don't forget to set their class names (if you have NSVC subclasses for them) and nib names.
  • A tab view created in a nib will have at least one tab view item. I'd recommend creating all of your items there and simply replacing their views (with the VCs' views) at run time.
  • You should do the work of replacing the tab views' items with the VCs' views in windowDidLoad. Make sure the window is not set to be initially visible; you'll want to show it only after you populate at least the first tab view item.

One note on tab views created in nibs: The item (tab) that will be initially visible is whichever one you had visible in the nib. Make sure you switch the tab view back to its first tab before saving.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top