domingo, agosto 08, 2010

Interface Builder Tutorial

The text is taken from: http://www.insanelymac.com/forum/index.php?showtopic=91735, but the screenshots are mine, upon following the tutorial, since the PDF the forum mentions doesn't seem to exit anywhere.

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 ~/ (expanded: /Users//).

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:
CODE
#import

@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:
CODE
#import

@interface Controller : NSObject {
IBOutlet id textField;
IBOutlet id slider;
IBOutlet id setToAmountField;
}

@end


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:

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


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:

CODE
-(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):

CODE
-(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:
CODE
#import

@interface Controller : NSObject {
IBOutlet id textField;
IBOutlet id slider;
IBOutlet id setToAmountField;
}
-(IBAction)reset:(id)sender;
-(IBAction)setToButtonClicked:(id)sender;
-(void)setAllTo:(int)numberToSetTo;
@end


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:
CODE
#import "Controller.h"

@implementation Controller
-(IBAction)reset:(id)sender
{

}
-(IBAction)setToButtonClicked:(id)sender
{

}
-(void)setAllTo:(int)numberToSetTo
{

}
@end


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:

CODE
[receiver doThis:withTheseArgs];


or if there are no arguments:
CODE
[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:

CODE
#import "Controller.h"

@implementation Controller
-(IBAction)reset:(id)sender
{
[slider setIntValue:50];
[textField setIntValue:50];
}
-(IBAction)setToButtonClicked:(id)sender
{

}
-(void)setAllTo:(int)numberToSetTo
{

}
@end

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:

CODE
#import "Controller.h"

@implementation Controller
-(IBAction)reset:(id)sender
{
[slider setIntValue:50];
[textField setIntValue:50];
}
-(IBAction)setToButtonClicked:(id)sender
{
[self setAllTo:[setToAmountField intValue]];
}
-(void)setAllTo:(int)numberToSetTo
{

}
@end


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:
CODE
#import "Controller.h"

@implementation Controller
-(IBAction)reset:(id)sender
{
[slider setIntValue:50];
[textField setIntValue:50];
}
-(IBAction)setToButtonClicked:(id)sender
{
[self setAllTo:[setToAmountField intValue]];
}
-(void)setAllTo:(int)numberToSetTo
{
[slider setIntValue:numberToSetTo];
[textField setIntValue:numberToSetTo];
}
@end


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.
CODE
#import "Controller.h"

@implementation Controller
-(IBAction)reset:(id)sender
{
[slider setIntValue:50];
[textField setIntValue:50];
}
-(IBAction)setToButtonClicked:(id)sender
{
[self setAllTo:[setToAmountField intValue]];
}
-(void)setAllTo:(int)numberToSetTo
{
if(numberToSetTo >= 0 && numberToSetTo <= 100) { [slider setIntValue:numberToSetTo]; [textField setIntValue:numberToSetTo]; } else } @end


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.

Change your code to this:
CODE
#import "Controller.h"

@implementation Controller
-(IBAction)reset:(id)sender
{
[slider setIntValue:50];
[textField setIntValue:50];
}
-(IBAction)setToButtonClicked:(id)sender
{
[self setAllTo:[setToAmountField intValue]];
}
-(void)setAllTo:(int)numberToSetTo
{
if(numberToSetTo >= 0 && numberToSetTo <= 100) { [slider setIntValue:numberToSetTo]; [textField setIntValue:numberToSetTo]; } else NSBeep(); } @end


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
.