A full Cocoa/Xcode/Interface Builder Tutorial

Posted 04 March 2008 - 09:54 PM

stroke

InsanelyMac Sage

Members

350 posts

Location:Russia

I went ahead and made a full Xcode/Interface builder tutorial for you folks at insanlymac. It's pretty big, so I can't show it in it's nice form here. However, there is a PDF file at the bottom as well as a shortened-down version here. It has no images, but the PDF has both images and colors.

Completed Application.

I realise there is another tutorial already, but that is more of an Interface Builder tutorial, with barely any use of Xcode aside from creating and building the project. And yes, I also realise this is a hopelessly simple application. However, it is for beginners. Hope you like it...

First and foremost, make sure you have the developer tools, which you can get with a free account from ADC. After installation, browse to your /Developer/Applications folder and find Xcode, and open it.

After you open it, you should be presented with a simple setup assistant (use default settings) as well as a "welcome screen" which can be safely closed out (or you can browse around it if you feel the need to). Go to File->New Project (Shift - Command - N), and select Cocoa Application.

In the next screen, name the project "My Cocoa Application" and press Finish. If you didn't change the directory of the project at that screen, it's default location is ~/<Project Name> (expanded: /Users/<user>/<Project Name>).

Now you are presented with the main Xcode window. On the left column is a list of all relevant files: classes, which contain the classes of your project, other sources, such as the main.m and prefix headers, resources, which include the interface document (MainMenu.nib in this case), required frameworks, and products (the outcome of compiling your project, in this case My Cocoa Application.app). The Target is the group of all files that make the product.

Classes are similar to the source files of any application, however they can be used interactively with each other. A method, which you will here about, is basically a function in a class. A method is also a message sent to a class. A function can be called repeatedly from different areas of code to do a repetitive set of instructions.

The first thing we want to do here is create a new class for controlling the program, aptly named "Controller." Right click the Classes folder in the file list, and select Add->New File. In the resulting window, select "Objective-C Class" from the list. In the next section, enter "Controller" as the file name, and click Finish without changing any settings.

Now you should have had a new window open up, just close out of it. Right now we don't need to edit any of these files. Now we can get on to making the interface. The interface file is a .NIB file, which means NeXT Interface Builder. Un-disclose the "Resources" group in the file list, and double click MainMenu.nib. Interface Builder, an app bundled with the Developer Tools should now open up. At first you are presented with an empty window, a document window listing the elements in the NIB, and a MainMenu bar. The empty window is simply the window for your application, which as it is now doesn't have anything in it.

The Main Menu bar above is the same menu bar that will appear at the top of your screen when your app is running. To make it say other than "NewApplication," double-click that area, and type in "Cocoa Application." Now single-click the same spot, to open the menu. Double-click each menu item to change it's name to match the application we're making. You can also do the same for the item in the Help menu.

The Menu Bar's modification is complete, so you can close that window. Now we can add some parts to the main window. Controls, or things you can interact with in a window (buttons, sliders, lists, etc) are stored in the Library Palette. To bring up this palette, do Shift - Command - L. You should see a grid of different controls you can put in your window, and you can simply drag and drop them in. Let's drag a slider (NSSlider) and two text boxes (NSTextField). Also drag two buttons (NSButton).

It really doesn't matter how you arrange the controls. You can put them where ever you want to. I do suggest putting one text box directly next to one button, and you'll see why a bit onwards.

Now hit Command - Shift - I (I as in Interface) to bring up the inspecter. Here you can adjust settings for individual controls like the ones you just dragged in. Make sure you have the first tab of the Inspector selected (look just below the palette title). We will change the names of the buttons, and there are two ways to do this: one is to double click the text on the button itself, and two is to do it through the inspector. I will simply double click the text on the button, and edit it so my left button says "Set to:" and my right button says "Reset." Select the two text field items on the window (shift - click each item), and head to the Inspector. If items are of the same class (in this case, NSTextField), you can edit attributes simultaneously. I want to make mine a bit nicer looking, so I will change the ends of the boxes to rounded. Do this by selecting the last tab under the Border header.

Now you're text boxes should be nice looking and rounded, but there's something we are going to want to do with the box next to the "Set to:" button, and that's add a number formatter. A formatter only makes certain text enterable into a text field, and we want this to be 0-100 for this box. Drag an NSNumberFormatter (the square with the money sign) from the Library right onto that text field next to the button. Now if you select the text field, a little circle should appear under it with a money sign in a square on it. Click this to bring up the formatter attributes in the Inspector.

In the Inspector, there should be a drop-down box a the very top that says "Mac OS X 10.4+," but for ease-of-use, click this and select "Mac OS X 10.0+." Now you should clearly see presets on the bottom and some settings up top. In the presets list, select the item with 100 under the "Positive" column and -100 under the "Negative" column. In the "Minimum" text box, enter 0, and in the "Maximum" text box, enter 100. This text field now only accepts whole numbers between 0 and 100.

However, it doesn't do anything right now, except format the number box. By adding code, we can change that, and make it do a very simple task. Go back to the Xcode window now, and find your Controller.h file in the file list. What you need to do here is kind of tricky at first, but very easy to understand after you do it the first time. Controller is a subclass (a "branch-off") of the main superclass, NSObject. Every class in Cocoa is an NSObject at it's root. Right now, we need to instantiate Controller in our NIB file to allow it to interact with the controls in the future. Instantiating is, sort of, when you make a usable "copy" of an object that can send and receive messages. Remember that methods (basically functions) can be messages.

So, we need to drag our header file (Controller.h) in the Document window of our MainMenu.nib file. If you haven't already, re-open MainMenu.nib in interface builder, and make sure the document window is showing (look below to see the document window). Keep Interface Builder active ("on top" of every other window), but make sure you can see the file list in Xcode's window behind it. Click the "H" icon directly to the left of the Controller.h file in Xcode for a few second, then drag it into the document window. When hovering over the document window, you should see the add cursor (the one with the green plus). When you drop it in nothing will happen.

We need to actually instantiate the Controller class now. Do this by dragging an NSObject (the plain blue cube) item from the Library palette straight into the document window.

Open the Inspector Palette (Command - Shift - I), and go to the Classes tab at the very top. The Classes tab is the dark blue circle with the white "i" on it. You will see a label that says "Class" with a drop-down box right next to it. In this drop-down box, type in the name of the class you want it to be � in this case the one we just instantiated - Controller. You will need to instantiate custom classes before using them in this way.

Now you have a working instance of the controller class. Onto the next step, which actually involves coding, finally. Don't close IB (Interface Builder) yet, though, we will need it soon enough.

Go back to Xcode, and in the file list, select Controller.h. Go to View -> Zoom Editor In to open up the in-window editor (or do Shift - Command - E). Right now in this header file you should have this exactly:

#import <Cocoa/Cocoa.h>
@interface Controller : NSObject {
}
@end

Because this class will be interacting with the NIB file, we need to add IBOutlets, which allow you to connect this object to some on-screen controls. For this, the IBOutlet type will be id, which can stand for any object. Edit the header file (Controller.h) to look like this:

Now you have three outlets in interface builder you can connect to the object. We still need to make our methods, though. A method generally takes the following form:

-(<RETURN TYPE>)doThis:(id)arg1 andThis:(id)arg2; // (etc - there can be many arguments, or no arguments)

<RETURN TYPE> is the type of variable you want the method to return. For this application, we will be using void which doesn't return anything. id is a variable type that, as you read, stood for any object. You can replace it with some sort of variable that is the only variable or class that can be passed into the method:

-(void)doThis:(NSString *)arg1 andThis:(NSString *)arg2;

The above method returns nothing, and can only take NSStrings as arguments.There is a special kind of method setup for methods that are activated from an interface (with controls, etc):

-(IBAction)doThis:(id)sender;

IBAction is just a notational way of doing void, but the (id)sender piece is required for Interface Builder to properly connect the actions (methods). So... let's make a method.The first method we want to do is a reset method, to reset everything back to zero, which will be triggered by the Reset button. We also want to make a set-to method, but remember, IBActions can only take one argument (sender). So we'll need to make two methods to accomplish that. Edit your header file to look like this:

The setToButtonClicked: method will later trigger the setAllTo: method. Right now, we can hook these things up in Interface Builder. Connecting outlets and actions simply requires Control - dragging from one object to another. To set one of objectA's outlets to objectB, you drag from objectA to B. To have objectB activate one of objectA's methods, you drag from B to A. Let's set the outlets of the Controller object. Go to interface builder, and control drag from the blue Controller cube in the document window to the top text field, and in the resulting HUD window, select the "textField" outlet. Do the same for the slider, and the setToAmountField, which will be the text field right next to the Set To: button.

Of course, now we want to hook up our actions/methods. To do this, drag from the object that will trigger the action to the Controller object, and select the proper action. For the Reset button, select the reset: action, and for the Set to: button, select the setToButtonClicked: action.

For the extent that this tutorial goes, the interface is done. You can close IB. However, it's not a polished interface and some features that would be expected are missing. Head back to Xcode. Copy those three methods we wrote in Controller.h to Controller.m, under @implementation Controller so it looks like this:

Be sure to remove the semicolons from the end of those lines (just those lines). This .m file, or implementation file, is where the instructions are mainly stored. Our header file tells us we have these methods, and the implementation file shows us what they do.Cocoa is full of predefined classes, 3 of which we have in our program here (NSSlider, NSTextField, NSButton). These classes each have their own methods you can use to do something to the class. Methods are sent in this fashion:

[receiver doThis:withTheseArgs];

or if there are no arguments:

[receiver doSomething];

The receiver is the object getting the message (doThis: and doSomething in these cases). We are going to have three objects that need to be sent messages: slider, textField, and setToAmountField. For slider and text field, we will be using one method (it works for both classes - most classes do not share most methods): setIntValue:. It does just what it seems like, it sets the integer value of the receiver. setToAmountField will be getting a different message: intValue. Instead of setting the intValue, it gets it from the receiver. Let's look at our reset: method.

We want this method to reset everything back to 0, so we will somehow be using setIntValue:0 somewhere. There are two objects that will receive this: slider and textField. Let's implement that under the method in the implementation file:

As you can see, it tells the slider and the textField to set their values to 50. However, we want the user to be able to set it to a custom amount, which is what the other two methods are for. We want the setButtonClicked: method to simply call the other method, setAllTo:, with an argument (because IBActions cannot take any arguments other than sender). Since the setAllTo: method is within the same class, we use self as the receiver. But remember, we want to pass an argument that's the int value of the setToAmountField text field. So, what do we do? Well, we call another method, as you read: intValue. This can all be done in one line by separating with brackets, the standard Objective-C convention:

That one line really calls two methods, and passes the result of one as the argument of another. It doesn't do anything, yet, because setAllTo: is empty. We can basically use the same code as reset: with a slight change, and that's using the passed argument: numberToSetTo:

However, the number formatter we did earlier does not work if the method simply grabs whatever is in the text field at the time. So, we need to do an if�else statement to determine if the value is greater or equal than 0 or less than or equal to 100.

As that is, it doesn't do anything at all if it's less than 0 or greater than 100, which would be confusing. So, we are going to do something really easy to fix that. Right now, if you build your application (with the Build and Go button in the toolbar), it will work, so you've made a Cocoa app.

else NSBeep(); means that if the value is not greater than 0 or less than 100, it will just make a beep noise. NSBeep() is not a normal method, it's a global C function that can be called from anywhere.

Posted 16 June 2008 - 09:21 AM

Posted 16 June 2008 - 02:57 PM

furKing

InsanelyMac Protégé

Members

6 posts

(NOOB warning) -- Any idea why I can drag and drop buttons into the Window but not the slider or text boxes? Using XCode 3.1 -- (It seems if I try to do this in XCode 2.5 it works but then I have trouble later in the tutorial because things have changed a bit.)

Baffled. All the buttons drop in but anything from the "Cells" just flys back out again. I'm sure I'm missing something...

EDIT: Please disregard. I was dragging from "Cells" and should have been dragging from "Inputs & Values." I'll leave this post up in case some other NOOB has the same problem.

Posted 28 July 2008 - 03:42 AM

vladio

InsanelyMac Protégé

Just Joined

1 posts

I'm working on a noob project that has 3 text fields. The top is labeled "Mileage", the one directly under is labeled "Gallons", under that is a button labeled "Calculate", under that is a text field that displays the calculation and is labeled "MPG". I have the app working fine but thought it would be nice to reset everything so I added another button at the bottom and labeled it "Reset". I added the action and have it set to reset all the field to 0. I really don't like the appearance of this though and would like to just reset the whole thing to what it looks like when it's launched - each field with it's label displayed instead of 0's and the top label highlighted ready for input. Is this doable? If so, how? I did a search and it brought me here so I hope someone has the patients to offer a bit of advice.

I appreciate anything you can offer to get me going in the right direction.
Joe

Posted 28 December 2008 - 05:59 AM

iPhone-Guy

InsanelyMac Protégé

Just Joined

2 posts

Beginner here. I know I must be doing something right... because DID get this much working:
1st slider... when moved... shows a value in a label #1.
2nd slider... when moved... shows a value in label #2.

But what would be the IBAction and IBOutlet syntax... if I tried to do this instead:
When either of the 2 sliders are moved... values for BOTH sliders are shown in label #1.
(Label #2 won't be needed at all.)

I keep getting compile-time errors like: "first use not defined" for the sliders.

Posted 17 April 2009 - 10:47 PM

shamanthVohra

InsanelyMac Protégé

Just Joined

1 posts

hi i've been trying the pdf tutorial and it works great... up to a point.. The problem comes when i try to hook up the slider with the ctrl click and drag. It works fine for textboxes which are highlighted as soon as i hover over them but when i ctrl click the controller and then drag the blue line to the slider nothing happens.. i am using interface builder 3.1.2 by the way. I would appreciate any help.

PS.: it does work the other way around though, i can ctrl click and drag from the slider to the controller and it offers to set one of the methods but i guess that doesnt really help..

EDIT: ok i think I have it. The problem was that when I defined the IBOutlets as type id interface builder didnt recognize them as compatible so I had to change them to the specific matching types..

Posted 03 June 2009 - 06:38 PM

jeffreyrdiamond

InsanelyMac Protégé

Just Joined

1 posts

I loved this tutorial! Thank you! I had two problems I was hoping someone would comment on:

1) I couldn't get the formatter to work at all. I tried both the 10.4+ and 10.0+ versions. No constraints I entered had any effect at all. Even though I could see the little brackets by the SetTo input window with the min/max constraints.

2) I wanted to have the displayed value also reflect the initial value of the slider (50), but it starts out blank until you change it. Any attempts I made to change it on the controller classe's Init function either did nothing or crashed the machine. (Would this work if I had a view class?) So I did the inelegant approach of creating a timer with a callback that sets the values. That works, but there has to be a better way to give a text field an initial value.

Posted 30 July 2009 - 10:15 AM

Gannet

InsanelyMac Protégé

Just Joined

1 posts

Dude. This is by far the best tutorial for a beginner to Xcode I have found. I have only started coding on my new Mac this week and installed iphone sdk 3.1. All the other tutorials I found were for older versions of Xcode and I could not follow them past the hooking up of the Outlets and Actions.

Thanks so much for the post it has helped me heaps with getting to grips with, lets face it, a greatly different coding experience to your Java, Visual Studios OO IDE.

Since getting a new Mac I have remembered why I got my G3 years ago. You use a pc but you experience a mac.

Posted 02 October 2009 - 03:13 PM

Tom Hanson

InsanelyMac Protégé

Just Joined

1 posts

Hi,
I am very new to Xcode. I have started the tutorial and when I get to the open .nib file I have only MainMenu.xib available. Have I done something wrong? Oh yea, I am using version 3.2 in snow leopard
Thanks for any help you can give.