cocoanstabview

How do I set initial view in an NSTabView?


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];
}

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.

    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.