3
votes

To be honest, I don't even know where to start. I feel like I've been going around in circles for hours trying different things.

My issues are around how to configure NSWindowControllers, NSViewControllers, and NSViews in IB and then access each in a hierarchy to switch out the NSViewControllers using a single NSWindowController...

I started with this code from the Apple site and am trying to alter it to fit my situation: https://developer.apple.com/library/mac/samplecode/ViewController/Listings/MyWindowController_m.html#//apple_ref/doc/uid/DTS10004233-MyWindowController_m-DontLinkElementID_12

I have a small app that has 2 views that need to be switched back and forth depending on user interaction. Let's call them drop_view and table_view. Drop_view is the view that is loaded on launch. Both of my views are configured in separate nib files:

RADropViewController.xib

  RADropViewController.m
  RADropViewController.h
  RADropView.m
  RADropView.h

RADropViewController.xib

  RADropViewController.m
  RADropViewController.h
  RADropView.m
  RADropView.h

Each nib has a File's Owner, First Responder, Application, View Icon, and Object. The File's Owner is set to the controller class, the View Icon is set to the View class, and the Object icon is set to the View Controller class.

Then I have a window controller nib with a window controller.

RAWindowController.xib

RAWindowController.h
RAWindowController.m

The nib has File Owner set to RAWindowController, Window set to NSWindow, and an Object set to RAWindowContoller. I also have a Custom View in the window in this nib because there is an NSView outlet in the Apple example and I read on stackoverflow in all of my research that "there is usually a window controller with a host view and which is used to host the different NSWindowControllers).

The outlets are:

File's Owner

Outlets
    myTargetView -> Custom View
Referencing Outlets
   delegate -> Window - Window

Window - Window

Outlets
    delegate -> File's Owner
Referencing Outlets
    window -> Window Controller

Window Controller

Outlets
    myTargetView -> Custom View
    window -> Window - Window

Here is my RAWindowcontroller.h

@class RADropViewController, RATableViewController, RAWindowView;

@interface RAWindowController : NSWindowController
{

    IBOutlet NSView *myTargetView;
}

@property (nonatomic, assign) NSViewController *myCurrentViewController;

@property (nonatomic, strong) RADropViewController *dropViewController;
@property (nonatomic, strong) RATableViewController *tableViewController;

-(void)changeViewController:(NSInteger)whichViewTag;
- (NSViewController *)viewController;


@property (strong) IBOutlet NSView *myTargetView;
@end

and my RAWindowController.m

#import "RAWindowController.h"
#import "RADropViewController.h"
#import "RATableViewController.h"

@interface RAWindowController ()

@end

@implementation RAWindowController

@synthesize myCurrentViewController, myTargetView;

enum // popup tag choices

{
    kDropView = 0,
    kTableView,
};

NSString *const kDropViewTitle    = @"RADropViewController";
NSString *const kTableViewTitle   = @"RATableViewController";

- (id)initWithWindow:(NSWindow *)window
{
    self = [super initWithWindow:window];
    if (self) {
    }
    return self;
}


-(void)awakeFromNib{
    _dropViewController = [[RADropViewController alloc] initWithNibName:kDropViewTitle bundle:nil];
    _tableViewController = [[RATableViewController alloc] initWithNibName:kTableViewTitle bundle:nil];
    [self changeViewController:kDropView];
    [myTargetView addSubview:[self.myCurrentViewController view]];
    [self.window center];
    [self.window setContentMaxSize:NSMakeSize(409.0, 295.0)];
    [self.window setContentMinSize:NSMakeSize(409.0, 295.0)];
}

-(void)windowWillLoad{
    [super windowWillLoad];
}

- (void)changeViewController:(NSInteger)whichViewTag
{
    if ([self.myCurrentViewController view] != nil)
    {
        [[self.myCurrentViewController view] removeFromSuperview];
    }
    switch (whichViewTag)
    {
        case kDropView:
        {
            if (self.dropViewController == nil)
            {
                _dropViewController = [[RADropViewController alloc] initWithNibName:kDropViewTitle bundle:nil];
            }
            myCurrentViewController = self.dropViewController;
            break;
        }
        case kTableView:
        {
            if (self.tableViewController == nil)
            {
               _tableViewController = [[RATableViewController alloc] initWithNibName:kTableViewTitle bundle:nil];
            }
            myCurrentViewController = self.tableViewController;
            break;
        }
    }
    [myTargetView addSubview:[self.myCurrentViewController view]];
}

- (NSViewController *)viewController
{
    return self.myCurrentViewController;
}

Here is some sample code from my drop_view that I call when I want to switch to my table_view

-(void)showDifferentViewController: (NSViewController *) controller{
    _windowController = [[RAWindowController alloc] initWithWindowNibName:@"RAWindowController"];
    [_windowController changeViewController:1];
}

So everything works as I would expect on launch. The drop_view gets loaded and all looks good. Things start to fall apart when I try to load the table_view.

This line of code:

[myTargetView addSubview:[self.myCurrentViewController view]];

is the issue, at least on the surface. myTargetView is nil. It's not nil on launch but is nil when I try to load a new view. So no new view gets loaded. My drop_view just stays there as nothing happened. myTargetView represents my host view (as I understand it). It's the custom view on my window in my nib file. You can see the outlet to it set up in my RAWindowController.h file and that connection looks OK to me. It seems like the reference is being lost maybe due to the host view being unloaded?

I thought maybe this post had saved me: How exactly does an NSView, an NSViewController, and MainMenu.xib fit together?

[self.window.contentView addSubview:self.customViewController.view];
[self.customViewController.view setFrame:[self.window.contentView bounds]];

...but I realized my self.window object is nil (I'm assuming because my window object is set to an NSWindowController in the .nib).

I have a feeling I'm failing to grasp something very basic about windows, controllers, how to configure them, and to access them programmatically. I appreciate any insight you have.

1

1 Answers

8
votes

Okay, so here's what I've picked up in the nine months since I asked the question you linked to. Anytime you're mixing and matching nibs, view controllers, and window controllers, things can get hairy. However, if you think positive thoughts, count to three, and knock on wood, sometimes things will fall into place.

This is how I mentally break down the various controllers:

NSWindowController (MyWindow.xib)
|
|-- NSViewController (MyFirstView.xib)
    |
    |-- NSView
        |
        |-- NSTextField
        |-- NSButton
        |-- NSImageView
        |-- Etc.
        |
|-- NSViewController (MySecondView.xib)
    |
    |-- NSView
        |
        |-- NSTableView
        |-- NSButton
        |-- Etc.

So in your situation, you'll want to do the following in RADropViewController.xib:

  1. Select the "File's Owner" placeholder object.
  2. Go to the Identity Inspector and make sure that RADropViewController is the class entered in the "Custom Class" field.
  3. Select your top-most view object.
  4. Again in the Identity Inspector, make sure that RADropView is the class entered in the "Custom Class" field.
  5. Right-click and drag from the "File's Owner" object to custom view object and connect the view outlet.

You've now connected the view controller to its view. The same thing can be done in code by doing the following in RADropViewController.m:

- (void)awakeFromNib
{
    self.view = [[RADropView alloc] initWithFrame:NSMakeRect(0, 0, 250, 250)];
}

Now repeat these steps in RATableViewController.xib (or RATableViewController.m if you decide to go the code route).

Just to be clear, you either connect the view in Interface Builder or in code, but not both. I used to accidentally override my views because I would set them in IB and then again in the -awakeFromNib method. Looking back, I see that I was basically redoing everything I did in Interface Builder, but until I really grasped the nib unarchiving process, it seemed like a logical thing to do. Anyway … back to the fun.

Now that your view controllers are all set up, hop over to RAWindowController.xib. Again, make sure the "File's Owner" object is pointing to your window controller class and connect the window to the window outlet.

In RAWindowController.h, make your view controllers outlets by adding the IBOutlet macro keyword to your view controller properties:

@property (nonatomic, strong) IBOutlet RADropViewController *dropViewController;
@property (nonatomic, strong) IBOutlet RATableViewController *tableViewController;

Now back in RAWindowController.xib, drag two view controller objects from the Object Browser to the sidebar. In the Identity Inspector, set the class for each to its respective view controller class and in the Attributes Inspector, set the Nib Name for each to the nibs you worked earlier. Now, connect the view controller objects to the properties you just created outlets for.

Finally, to switch the views, add something like this to RAWindowController.m:

- (void)changeViewController:(NSInteger)whichViewTag
{
    NSView *contentView = self.window.contentView;

    switch (whichViewTag)
    {
        case kDropView:
        {
            self.myCurrentViewController = self.dropViewController;
            break;
        }
        case kTableView:
        {
            self.myCurrentViewController = self.tableViewController;
            break;
        }
    }

    if ( contentView.subviews.count > 0 )
    {
        [contentView replaceSubview:contentView.subviews[0]
                               with:self.myCurrentViewController.view];
    } else {
        [contentView addSubview:self.myCurrentViewController.view];
    }
}

I'm pretty tired right now, so I may have missed something. I had to post, however, seeing as how I was struggling with this stuff not too long ago. I hope I've helped a little. If I was unclear about anything, please let me know, and I'll try to explain things better. I promise you this much, though: it will all make sense eventually. One day it'll just "click," and you'll laugh at this old Stack Overflow question. Anyway, good luck, and let me know if I can shed more light on anything you're unsure of. Take it easy.