views:

398

answers:

3

I agree with the answers given at http://stackoverflow.com/questions/569940/whats-the-best-way-to-communicate-between-viewcontrollers but what if I'm using the Interface Builder to design my application Views?

Without using the Application Delegate, how can I reference Model objects from within Controller objects?

As far as I can tell I cannot put custom Init methods that use dependency injection to pass references to the model into IB.

Forgive me as I feel I'm missing something very elementary here. I've looked at the Apple documentation for IB but haven't found a clear answer.

A: 

Usually you would not touch the model directly in IB. You wire your views up with your controllers, maybe bind some properties of the controllers to the views. Then you write code (in the controllers) to access the model. You can use the awakeFromNib method if you like, but you can also implement you custom init in the controller.

Nikolai Ruhe
+1  A: 

InterfaceBuilder represents the V in the MVC pattern. For this reason, you won't use anything you would create in IB to manipulate your models. You will use some sort of delegate between the IB created nib's and the models. That delegate reprsents the C in the MVC, and can talk to both the user interface as well as the underlying model.

TokenMacGuy
+2  A: 

The article your reference is a good one - like you I like to inject a model into the controller (an old habit from Smalltalk days - in particular Dolphin Smalltalk follows this pattern quite extensively).

I have found that most of my controllers have init methods like the following:

- (id)initWithItem:(id<Nameable>)item addingTo: (id<ModelContainer>)model {
    if (self = [self initWithNibName:@"ItemEditTableToolbarView" bundle:nil]) {
        self.namedItem = item;
     self.container = model;
    }

    return self;
}

You can then call your controller like this:

ReDoListEditController *itemViewController = [[ReDoListEditController alloc] initWithItem: item addingTo: model];
    [self.navigationController pushViewController: itemViewController animated: YES];
    [itemViewController release];

Notice that I embed the name of my Nib file in the init method (you could make that another parameter if you need more flexibility - but I haven't yet found any of my controllers need to be generic. This is interesting - as in Dolphin, controllers (and their views) can be very compositional - so often in your init method, you would be picking out sub models from your model and calling init for your sub components. I think this might be possible on the iPhone with IB but I haven't explored this idea yet.

However you need to do a bit of additional work to pass your model to the very first controller (this was something I hadn't initially figured out).

  1. Create a Window Based application in XCode (if you want navigation based app, you can add this with code fairly easily)
  2. Edit "YourAppDelegate.m" and change the applicationDidFinishLaunching: method to something like the following:

    viewController = [[AppRootViewController alloc] initWithModel: [application model]]; viewController.view.frame = [[UIScreen mainScreen] applicationFrame];

    [window addSubview: viewController.view];

    [window makeKeyAndVisible];

If you want to use another project type, the following instructions also seem to work but its a lot more effort (I simplified to the above):

  1. Delete your MainWindow.xib file (sounds radical, but you want to create the MainWindow yourself - you can still create all your other views with IB)
  2. Edit your .plist file and remove the MainWindow.xib entry (its usually the last item)
  3. Edit your main.m file and change the UIApplicationMain line (replacing the last parameter with the name of your existing app delegate) - e.g.:

    int retVal = UIApplicationMain(argc, argv, nil, @"YourAppDelegate");

  4. Edit "YourAppDelegate.m" and change the applicationDidFinishLaunching: method to something like the following:

    window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    viewController = [[AppRootViewController alloc] initWithModel: [application model]]; viewController.view.frame = [[UIScreen mainScreen] applicationFrame];

    [window addSubview: viewController.view];

    [window makeKeyAndVisible];

I have added a category method to UIApplication called:

- (MyModel *)model;

You can then get your model from the application (like I show above) and also in other places in your application e.g.

viewModel = [[UIApplication sharedApplication] model];

Although if you always pass it into your view controller like I have described, this is probably a code smell.

TimM
Thanks Tim. This is the best answer, but like you said, not 100% awesome! The message seems to be that serious developers don't use IB - surely that can't be right?
NiKUMAN
I have a hunch there is a way to do some of the initial startup in code such that you could launch that first Controller with code as well. I have a few samples to try and will report back.
TimM
Serious developers definitely use IB. As answered, it's just an issue with the root view controller of the application, not with all view controllers. You can still lay out everything managed by each particular view controller in IB just fine.
Chris Hanson