February 5, 2010

Your First Cocoa Application: XCode

You should have first gone through the Interface Builder walk through. You’ve created an interface with a button and a text field that don’t do anything. Strangely you didn’t have to write any code. When you run and build your app, the button works, and so does the text field.

By now you should have some vague sense of what Model-View-Controller means from the reading. It’s an abstraction invented with Smalltalk (again) to control the complexity of designing user interfaces. You also have the concept of delegation- you keep objects from having to know very much about each other. Model-View-Controller (MVC) is another pattern that allows you keep distinct aspects of your code separated. In particular it allows your models to be reusable. In this tutorial we are not going to generate a model. Our logic is going to be too simple for that.

We created a View with Interface Builder so nothing to be done there. The only thing that’s really missing is C, the Controller. Controllers tend to be the least reusable part of any project, this is because they tend to be very, very application specific. Any thing that truly emerges as a reusable pattern should probably be moved over to the Model or to the View.

But again, we are being abstract. So let’s create a Controller and see what that’s like. To create your very first controller we need to create the source files to define the class. Right click on the Classes group in the Groups & Files list view in XCode. Select Add > New File. In the Mac OS X half of the left selection pane select Cocoa. In the right pane select Objective-C class. Click Next and give it the name MyController. A new header and source file using your name should now appear in your Classes group.

Lets take a look at the header file. Hmm, looks pretty much like the last assignment except that you’re importing the Cocoa framework instead of the Foundation framework. It’s probably safe to assume that the Cocoa framework relies on the Foundation framework and is thus implicitly included.

cocoa6

Lets define some instance vars. Make your interface declaration look like the following:

@interface MyController : NSObject {
   IBOutlet NSTextField* myField;
   IBOutlet NSButton* myButton;
}
@end

Hmm what the heck is IBOutlet? It isn’t anything at all! It just allows Inteface Builder to parse your source file for instance variables which you say you want to hook up to components defined in your nib file. That’s all it is. Again, you are simple letting Interface builder know that you want these variables to be assignable from Interface Builder.

But Interface Builder doesn’t yet know about our controller. Let’s inform it of it’s existence. Switch to Interface Builder. Hmm… how can Interface Builder know about the existence of a class in XCode?

Try typing Object into the Library search field. Wow you can actually add Objects from Interface Builder to a nib file. Pause for a moment to think about what is about to happen.

You are about to let Interface Builder know that you want an instance of your controller class to be created along with your button and textfield when the application loads this nib file. Drag the NSObject item into the nib window (NOT your application window).

Hmm. But your class is not an instance of NSObject. It is a subclass of NSObject, MyController. We need to change the class to match our file. To do this we need to use the Inspector. Make sure Inspector is open and click on your NSObject. Select the 6th tab in the Inspector and type in MyController for the class (you’ll notice that Interface Builder will autocomplete this name, it knows about your classes).

cocoa7

Once you’ve done this right click on MyController in the nib. You’ll get HUD like display that shows the two outlets you defined in XCode. You can now assign these two outlets. Do that now. There are several ways to do this, but the new way is simpler. Just drag from the dot in the HUD to the object you want to connect to.

cocoa8

Connect the textfield as well. Save your nib file and switch back to XCode. Open up the MyController.m file and modify the implementation bit to look like the following:

@implementation MyController
 
- (id) init
{
  self = [super init];
  if (self != nil) {
    NSLog(@"Initialize MyController!");
    NSLog(@"myField: %@, myButton: %@", myField, myButton);
  }
  return self;
}
 
- (void) awakeFromNib
{
  NSLog(@"awakeFromNib");
  NSLog(@"myField: %@, myButton: %@", myField, myButton);
}
@end

init should look familiar from before. However we now have a curious new method called awakeFromNib. This is a special method that gets called only if your object is getting instantiated from Interface Builder, otherwise it doesn’t do anything.

Build and run your application and make sure the Console window is open. Notice that you assignments aren’t available in init. So clearly you object gets initialized, but it’s outlet member variables will not be set until awakeFromNib, this is EXTREMELY IMPORTANT.

Why? Because Objective-C lets you send message to nil. What is nil? nil is nothing! Thus nothing happens if you send a message to nil. Your program will crash if you believe otherwise. Again, when instantiating classes via Interface Builder, you must wait until awakeFromNib to reference any outlets.

So you’re controller now has references to the button and the textfield. First lets make the button do something. Since that is simplest.

Change you implementation of MyController to look like the following:

@implementation MyController
 
- (id) init
{
  self = [super init];
  if (self != nil) {
    NSLog(@"Initialize MyController!");
    NSLog(@"myField: %@, myButton: %@", myField, myButton);
  }
  return self;
}
 
- (void) awakeFromNib
{
  NSLog(@"awakeFromNib");
  NSLog(@"myField: %@, myButton: %@", myField, myButton);
}
 
- (IBAction) doSomething:(id)sender
{
  [myField setStringValue:@"Wassup!"];
}
@end

Also modify your interface to look like the following so that people out there in the world know that MyController does something ;) Note the signature. The action only takes on argument called sender. The type is id. This is a convention and a good one. sender is the object who called the message on the receiver. Make sense of the last sentence before moving forward.

@interface MyController : NSObject {
  IBOutlet NSTextField* myField;
  IBOutlet NSButton* myButton;
}
- (IBAction) doSomething:(id)sender;
@end

Now go back to Interface Builder. When somebody clicks on the button we want the button to send the message doSomething: to the instance MyController when our program starts up. You do this by Control-Click-Dragging from the button to the MyController in the nib window. When you release you should get a little HUD like display that lets you select a method. Select doSomething:.

Save your nib file and switch back to XCode. Run the application and you should see that clicking the button changes the text within the text field.
cocoa9

Lets talk about delegation. Perhaps you want to know when a user starts and stops editing a text field. You will need to make MyController a delegate of the textfield. You can do this interface builder, but it’s just as simple to do this with code. Change awakeFromNib to look like the following:

- (void) awakeFromNib
{
  NSLog(@"awakeFromNib");
  NSLog(@"myField: %@, myButton: %@", myField, myButton);
  [myField setDelegate:self];
}

Then after the doSomething: method add the following new method:

- (BOOL) respondsToSelector:(SEL)asel
{
  NSLog(@"MyController respondsToSelector? %@", NSStringFromSelector(asel));
  return [super respondsToSelector:asel];
}

Build and run your application. Make sure the Console window is open. Right when you app starts up you should see the textfield attempting to call some delegate methods on the MyController instance. Click inside the textfield and start typing. After some characters type the return key. Even more methods. This reveals the power of a dynamic programming system. By using reflection Apple can support many behaviors on textfield. As a programmer you are also freed from having to write giant switch statements based on event type. You can simple choose to implement a method or not.