views:

157

answers:

3

After always running monolithic blocks of code from within my AppController object, I've just learned how to modularize my code using separate model controller objects (so much neater and less confusing to work with :-))

My simple test app has a main AppController and two model objects (Model1 and Model2). I can successfully pass data from the AppController to the models, and the models themselves can run methods and process the passed data 'internally' as they were intended to do -- but I can't get them to communicate with a darned NSTextField in the UI. Here's the relevant parts of my code:

In AppController.m #import "AppController.h"

@implementation AppController

- (IBAction)passObjectsToModelController:(id)sender
{
NSString *stringToPass = @"Hello from Model2 :-)";
int numToPass=12345;

Model2 *ObjController2 = [[Model2 alloc]initWithStuff:stringToPass:numToPass];
    [ObjController2 release];
}

@end

...in Model2.h #import

@interface Model2 : NSObject
{
IBOutlet NSTextField *passedStringField;
}
- (id)initWithStuff:(NSString*)passedString :(int)passedNum;

@end

...and finally in Model2.m #import "Model2.h"

@implementation Model2

- (id)initWithStuff:(NSString*)passedString :(int)passedNum
{
if(self = [super init])
    {
    NSLog(@"now inside 'Model2' controller...");
    NSLog(@"the passed string reads: %@",passedString); //••• this works •••
    NSLog(@"the passed number is:%d",passedNum); //••• this works •••

    [passedStringField setStringValue:passedString]; //••• WTF!!... this DOESN'T work! •••
    // do something internally with passedNum here...
    }
return self;
}

@end

Both model objects have outlets to the common NSTextField and I've control-dragged from both objects to the field and connected them. My AppController doesn't know about the NSTextField (and I assume, doesn't even want to know). No IB connections have been made between the controller object and model objects.

NSLog tells me that the model objects are being created, and that the passed values are making it that far... but not from there into the text field in the GUI window. I'm not getting any compiler errors or warnings. Am I missing some kind of 'setTarget:' call perhaps?

Any help/ideas would be much appreciated. Thanks :-)

+1  A: 

The Controller sits between the Model and the View. The Model should not communicate with the View.

It should be the job of the Controller to pass any incoming values from the View to the Model. The Model then processes the data and sends back to the Controller which then updates the View with the new data.

So, in your code you should only have one IBOutlet for the TexField declared in the AppController.

Given all this, I am not exactly sure why the TextField is not being updated. From the given code looks like it should. Maybe multople IBOutlets are causing some issue? Can you try with only one Model having the IBOutlet?

Mihir Mathuria
Thanks, mihirsm. I only just got onto modularization so I'm not yet 100% up to speed on the Model-View-Controller concept, of course. However, my test is almost identical to Apple's "SimpleCocoaApp" example which also uses a controller and 2 model objects. The controller sends a string to either model, and the models (not the controller) write the string direct to the textField. Their controller isn't connected to the field -- only the two model objects are (just as I'm doing). Only difference is their demo sends via 'setTarget' and I'm using 'initWithStuff' (for more than one parameter).
Bender
I also tried (1) connecting just one of the models to the IBOutlet, (2) stripping the outlets from both models and adding one to the controller itself and (3) calling setFirstResponder on the textField thinking it might be a focus issue, but no luck yet unfortunately :-(
Bender
+1  A: 

Aside from the lack of MVC that mihirsm mentions, the actual problem is that you're trying to access an outlet in an -init method.

When a object is initialized, outlets are not guaranteed to be connected.

If you want to set the value of an NSTextField declared as an outlet, you should implement -awakeFromNib, which is called when the nib has been loaded and all outlets are guaranteed to be live.

in Model1.h:

@interface Model1 : NSObject
{
    IBOutlet NSTextField* passedStringField;
    NSString* modelString;
}
- (id)initWithString:(NSString*)passedString number:(int)passedNum;
@end

in Model1.m:

@implementation Model1
- (id)initWithString:(NSString*)passedString number:(int)passedNum
{
if(self = [super init])
    {
    //copy the string to our ivar
    modelString = [passedString copy];
    }
return self;
}

//awakeFromNib is called when our outlet is live
- (void)awakeFromNib
{
    [passedStringField setStringValue:modelString];
}

//don't forget to release the string, because we created it using -copy
- (void)dealloc
{
    [modelString release];
}

@end

Rob Keniger
G'day from Adelaide, Rob :-). Yes, I've got this now, thanks a lot for the assistance.
Bender
A: 

CORRECTION: I said that the main controller object wasn't connected to either of the two model objects. Sorry, this is wrong!! In fact the controller has outlets to both models, and AppController's header now looks like this:

#import <Cocoa/Cocoa.h>
#import "Model1.h"
#import "Model2.h"

@interface AppController : NSObject
{
// points to the Model1 object
IBOutlet Model1 *ObjController1;
// points to the Model2 object
IBOutlet Model2 *ObjController2;
}
- (IBAction)callController:(id)sender;
- (IBAction)passObjectsToController:(id)sender;

@end

The connections have been made in IB (and the local declaration in 'passObjectsToModelController' has been fixed so as not to hide the previously-declared instance variable) but still no results in the textField yet...

Bender
Yep, you need to implement `-awakeFromNib` as outlined in my answer.
Rob Keniger