Learn Cocoa II
In the
first tutorial in the Learn
Cocoa series, we started an application
called CocoaNotepad. Even without any custom code, the app has sophisticated text handling,
thanks to the Cocoa's built-in text functionality.
This tutorial will get you started with writing code. Rather than
explain the theory behind Objective-C, we're just going to dive right in and fill in the
gaps as we go. You might find it helpful to read the
C Language Tutorial first, but this
isn't required.
This tutorial is written and illustrated by
Scott Stevenson
YOU MUST READ THIS BEFORE CONTINUING
This tutorial has not been updated for Leopard and Xcode 3.1, so Steps 12-18
will not make sense. I will post
an updated version as soon as it is ready. If you
want to try to read through anyway, it is no longer necessary
to drag header files to Interface Builder. IB will find classes
automatically.
Copyright © 2006 Scott Stevenson
1
Thinking in Code
Stay FocusedThe goal here is initial exposure. Don't worry if you can't figure out how all the pieces fit together yet. Just stay focused on each step and "keep the faith."
All will be explained in time.
Code is the engine of an application. The user sees the interface, but that
interface only shows up on the screen and responds to user actions because
of the underlying code.
Being a Cocoa programmer means taking a concept and
figuring out how it can be expressed in code. In most cases,
code for a Cocoa application is written in Objective-C.
If you're new to programming, you might wonder what code is. It's
simply a collection of instructions. In fact, writing code for a computer
is similar to writing instructions for people. For example, take a look at
the following driving directions.
Enter I-280 South
Exit at De Anza ramp
Make a u-turn at Mariani Avenue
Turn right into 1 Infinite Loop
If this was Objective-C code, it might look more like the example below:
[car enterFreeway: @"I-280 South"];
[car exitFreewayAtRamp: @"De Anza"];
[car performUTurnAtStreet: @"Mariani Ave"];
[car turnRightAtStreet: @"1 Infinite Loop"];
When you write instructions for a person, you can leave certain details out because
they're obvious. Programming languages are more strict. You must
be explicit and consistent.
This table shows how the structure of the code can be broken down into smaller
pieces:
car |
enterFreeway: |
I-280 South |
car |
exitFreewayAtRamp: |
De Anza |
car |
performUTurnAtStreet: |
Mariani Avenue |
car |
turnRightAtStreet: |
1 Infinite Loop |
There are three parts to each line of code in this example: an object, a message,
and an input value. Not all Objective-C code is exactly like this. Some actions
have no input, for example.
2
Variables
Most of this tutorial focuses on practical examples instead of theory, but there are
a few ideas that need an explanation before we dive in.
A variable is simply a label for a container. It holds a value,
and the value can change at any time. A variable can hold any type of
data, such as text, images or numbers.
For example, a variable called
milesPerGallon might have an initial value of 22.
That same variable could be changed to another value.
An example of that is shown here:
milesPerGallon = 22;
milesPerGallon = 18;
Types 2.1
In Objective-C, variables must be declared before they are used. Here are some
simple examples:
int milesPerGallon;
id userName;
id currentPhoto;
Each of these lines declares a new variable, prefixed by a type.
The type states what kind of data the variable can hold. The variable
milesPerGallon is declared as an int, which is an
integer number.
The id type can hold any
object. If you don't know what an object is, don't get stuck on it now.
We'll learn more about that soon.
There are many variable types defined
by Cocoa itself, and you can create your own. For this tutorial, we're
only going to use the generic id type.
Assignments 2.2
An assignment is a line of code that assigns a value to a variable, just like
in algebra. For example, here we assign a line of text to a variable called order.
id order;
order = @"Four thousand lattes, please.";
The @ symbol in front of the text is an Objective-C convention, which we're not ready
to get into the details of just yet.
3
Methods
Methods are a list of instructions for the computer to perform. The list
is given a name, so that the same list of instructions can be performed
over and over.
For example, the list of instructions from the first section could be
grouped as one method called driveToWork, shown below:
- driveToWork
{
[self enterFreeway: @"I-280 South"];
[self exitFreewayAtRamp: @"De Anza"];
[self performUTurnAtStreet: @"Mariani Ave"];
[self turnRightAtStreet: @"1 Infinite Loop"];
}
The code to call the method in Objective-C looks like this:
[car driveToWork];
Calling the driveToWork method is essentially the equivalent of
calling all of those other lines of code individually. It's not just
to save space. If you need to change the contents of a method later,
you can do it in just one place.
Methods can have both input and output, though neither is required. Output
from one method can be used as input for another. Here are
some simplistic examples:
// no output or input
[car driveToWork];
// providing input
[car enterFreeway: 280];
[car setSpeed: 68];
[car setRadio: 105.3];
// multiple input
[car enterFreeway:280 atSpeed:68];
[car setRadio:105.3 withVolume:21];
// getting output
speed = [car currentSpeed];
distance = [car totalDistanceTraveled];
destination = [car targetDestination];
// multiple input with output
travelTime = [car estimatedTravelTimeAtSpeed:68 duringRushHour:NO];
We use assignments to store the method output in variables. For example,
the currentSpeed method returns a value which we assign to the speed
variable.
4
Writing Code
When writing code, things that may seem trivial can actually be very important.
Even leaving out a single character or putting particular parts of code in the
wrong order can cause errors.
When Xcode sees incorrect code, it can typically only display an error because
it doesn't know what your intentions are. It's up to you, as the programmer,
to fix the error. It's important to understand some basic formatting rules.
Word Spacing 4.1
The spacing between words in code might seem strange. In English, an instruction to exit the
freeway looks like this:
exit freeway at the De Anza ramp
In Objective-C, it looks like this:
[car exitFreewayAtRamp:@"De Anza"];
It's easier for the computer to understand the code if certain words
are grouped together. Not all methods are spaced exactly like this.
You'll learn more about this as you go.
End With a Semicolon 4.2
In English, a period (.) marks the end of a sentence. In Objective-C, each
instruction ends with a semicolon.
[object method:@"input"];
If you leave it out, you'll get error messages.
Some instructions span multiple lines. In those cases, you put a semicolon
at the end of the final line only:
travelTime = [car travelTimeAtSpeed: speed
duringRushHour: NO];
You can put these multiple-line instructions on one line if you like. Formatting
them across multiple lines is just meant to make the code easier to read.
5
Classes
Simplified
The examples in this section are designed to explain concepts more than
specific rules. They use a less formal structure than what you'd
find in most Cocoa code.
Many things in Cocoa are presented to developers as classes. For example,
a number is represented by the class NSNumber.
A
class describes either a real-world object or an abstract
concept by giving it specific
attributes and
methods.
There are whole books on the theories of classes and their
design, but we don't need all of that to get started. We're going
to focus on practical basics.
Adding Attributes 5.1
To write an application which manages photos, you might start by creating
a Photo class. Each photo has variables which define unique
attributes about the item, such as the ones listed below:
Photo
- Width
- Height
- Date
- Title
These variables are called instance variables, or
simply attributes. Here's a photo Objective-C class with the attributes
from above:
@interface Photo : NSObject
{
id width;
id height;
id date;
id title;
}
@end
The @interface statement begins the Photo class definition.
It inherits from NSObject, which means it has all of the same
functionality as that class.
Again, we're not going to get too stuck on the details
of what that means yet.
The next several lines list the attributes of a Photo, each one prefixed with
id. This is how Objective-C knows that these are attributes.
Not all attributes are id, but we can do it for simplicity here. The class
definition ends with @end on the final line.
Adding Methods 5.2
In addition to attributes which store information, classes also have methods,
which perform actions. For example, you might want to do the follow things with a
photo:
Photo
- Desaturate
- Display
- Delete
We can express these as methods in our Photo class:
@interface Photo : NSObject
{
id width;
id height;
id date;
id title;
}
- desaturate;
- display;
- delete;
@end
The methods are declared after the brackets, but before the @end statement.
Each instance method is prefixed with a single dash. These methods don't do anything
yet, we've just stated that they exist.
This pattern of starting with basic class name, then adding attributes and methods is
the basis for all object-oriented programming.
6
Objects
A class is just an abstract description. In most cases, you need to make an
instance of the class to do useful things with it. An instance of a class
is called an object.
Consider the schematics for an apartment complex. You can't live in the schematics.
Somebody has to take the schematics and create an actual building from them. In fact,
you can create many instances of the building in different locations using the same
set of plans.
Below is an example of how to make two photo objects in Objective-C. Again,
keep in mind that this code is simplified to focus on the concepts rather than the
rules.
id photo1 = [[Photo alloc] init];
id photo2 = [[Photo alloc] init];
Both photo1 and photo2 are objects. Specifically, they're
instances of the Photo class. Any attributes we add to the Photo class
will become available to these objects. Let's see how we would go about using
the methods we declared in the Photo class:
[photo1 desaturate];
[photo1 display];
[photo1 delete];
[photo2 desaturate];
[photo2 display];
Since these are two separate photo objects, the actions performed on each are
specific to that object. In other words, if we use -desaturate on
photo1, the photo2 object is unaffected. In
fact, we can delete photo1 without having any impact on photo2 at all.
Now, to be fair, the above code wouldn't work because we haven't defined
what each of these methods actually does. That would be fairly complicated
at this point, so we're going to move onto other basic concepts.
7
Using Xcode
Xcode is what most Cocoa programmers use to write code. There are other options and some of them
very good. To keep things simple, though, we're going to focus on Xcode.
Xcode's editor is specifically designed for programming, so it has some significant advantages
over general text editors. Xcode is more than a text editor, though. It's your "base of
operations" for writing Mac software.
For most projects, your code will be spread across dozens of files. Large projects have
hundreds of files or more.
When you compile the project, Xcode combines the files into
a single double-clickable application, along with supporting media, such as images
and help files.
Xcode might seem overwhelming, but don't get discouraged. Even
expert developers don't know what every single button, checkbox, and menu item does.
You can start by learning about the parts that are relevant to what you
want to do.
9
Create a New Class
Re-open the
CocoaNotepad project from the
first tutorial.
Click on the
Classes group on the left side of the window then and choose
File →
New File from the menu bar.
Select Objective-C class from the Cocoa group, then click Next. Many
of the names are similar, so choose carefully. You'll be asked to choose a file name for the class:
Type AppController.m for the file name, and
leave everything else at the default settings. Click Finish to add
the new file to the project. You should now see them listed in the Classes folder.
Now that these files are part of the CocoaNotepad target, the code from them
will be compiled into the application when you choose Build and Run. Of
course, the class doesn't do anything yet.
10
The Header File
A header file provides an overview of a class. It lists the variables
and methods that the class offers, but doesn't contain the details of
how those things work. It's somewhat like a table of contents.
First, we're going to add a variable. In fact, it's a special kind of
variable, called an IBOutlet, which is short for Interface
Builder Outlet. This variable holds a connection to a user
interface item, such as a text field. The connection is wired up
visually using Interface Builder.
With that connection in place, you can send instructions to the user
interface item. For example, you could enable or disable it, move it,
or change its appearance. You can also use IBOutlets for
objects which don't appear onscreen.
Add an Outlet 10.1
Double-click on AppController.h to open it in an editor
window. Make sure you open
AppController.h, not
AppController.m. Edit the contents of the file to look like this:
// AppController.h
// CocoaNotepad
#import <Cocoa/Cocoa.h>
@interface AppController : NSObject {
IBOutlet id textView;
}
@end
Most of the text was probably filled in by Xcode. In
most cases, you should only have to add the IBOutlet line. This
line says that AppController will have a connection to something
called textView. It's literally an outlet — you
plug the text view into it.
The lines that start with a double slash are comments, and have no
affect on how the application works. They're just there as notes
to yourself and anyone else who may see your code.
Add a Method 10.2
Next, we're going to add a method. Remember, a method is a list of
instructions which are grouped together. Change the contents of the file to
look like this (the comments are not shown):
@interface AppController : NSObject {
IBOutlet id textView;
}
- (IBAction) clearText: sender;
@end
An IBAction is a special kind of method which, much
like an outlet, can be wired up in Interface Builder. This method
will be used with a button.
11
The Implementation File
Double click AppController.m, which is the implementation file. The
implementation file contains all of the details about how the class
behaves. It's the counterpart to the header file.
Change the file to look like the code below, adding the clearText method:
#import "AppController.h"
@implementation AppController
- (IBAction) clearText: sender
{
}
@end
Note that the method does not end with a semicolon. Because this
is the implementation of the method, the method name is followed
by a pair of brackets. Add the following code between the brackets:
[textView setString: @" "];
The textView outlet will be connected to an object. Specifically,
an NSTextView object. Since it's an object, we can call methods
on it, just like we did with photo1 and photo2 earlier.
In this case, we're calling the setString: method, which
changes the contents of the text field. The input for this method
is @" ", which amounts to a single blank space. In
context, it should look like this:
#import "AppController.h"
@implementation AppController
- (IBAction) clearText: sender
{
[textView setString: @" "];
}
@end
At this point, you might be a bit confused. It's okay, that's normal.
We could try to explain how all of this works,
but the explanations will make much more sense after you see the
final result in action.
So let's wire everything
up in the user interface first, then try it out in a working
application. You can always come back and look at the details of this
code more closely. For now, save AppController.m and close it.
12
Add Header to NIB
In order to use the new AppController class in the user interface,
we need to add it to the MainMenu.nib file which we created in the first
tutorial. Unfold the Resources folder in the main Xcode window and
double-click MainMenu.nib to open it in Interface Builder.
Now drag AppController.h
from the Xcode window and drop it on the Interface Builder document
window. The document window will then switch to the Classes tab.
Once you drop the header file, the document window will
switch to the Classes tab. Click on the list view toggle control at
the left side of the document, just below the tabs. It looks similar
to the list view toggle in the Finder.
Select AppController in the list and choose Tools → Show
Inspector to bring up the inspector window. Make sure Attributes is
selected from the inspector dropdown.
You should now see Outlets and Actions listed in the inspector window. Click
on the Outlets tab, and you should see textView listed.
If you click the Actions tab, you should also see the
clearText: method which you created.
13
Create an Instance
Now that the AppController class is part of the NIB file, we need to
make an instance. Make sure the AppController class is still selected in the
document window and choose Classes →
Instantiate AppController from the menu bar.
The document window will switch to the Instances tab, and you'll see
a blue cube labeled AppController. This is your object instance.
Conceptually, this is similar to doing [[AppController alloc] init] in code, but creating
an instance in Interface Builder allows us to wire up connections visually.
The tiny orange exclamation mark icon tells you that there is at least
one unconnected outlet. So let's connect it.
14
Connect the Outlet
Double-click the Window icon to open your application window.
Hold down the Control key and drag a line from AppController to the
text view in the application window. You can release the Control key as soon
as you begin the drag.
Make sure you're starting the drag at AppController and moving towards the text
view, not the other way around.
When you release the mouse button, the Inspector window will pop up with the Connections
section displayed. The textView outlet should be selected. Click
Connect to activate the connection.
Once a connection is active, a silver dot appears next to the outlet name.
The Destination column now reads NSTextView, which is the class of the object that
the outlet is connected to. Now that this connection is in place, we can call
methods on the text view.
15
Add a Button
We're going to add a button which calls the clearText: method. First,
let's make some room. Resize the text view by click on it and dragging the bottom
handle up a bit.
Now that we've made some space, we can add the actual button. Open
the object palette by choosing Tools → Palettes → Show Palette
from the menu bar.
Click on the Cocoa-Controls toolbar item in the palette, which is usually the second one from the
left. There's a control simply labeled Button. If you hover the mouse
pointer over it, a tooltip reveals its class name — NSButton.
Drag the button
over to the application window and drop it in the space you've made below the text view.
Change the button title to "Clear Text" by double-clicking it, then press Return when
you're done editing it.
Make sure the button is still selected and open the Inspector with Tools →
Show Inspector. Choose Size from the Inspector dropdown.
In the Autosizing box, click the top and left segments of the outer rectangle. This
tells Interface Builder to allow the outside of the button to grow and shrink with
the window, keeping the button itself pinned to the lower right.
You could create an IBOutlet for the button, but there's no need to do
that in this case since we don't need to call any methods on it.
16
Connect the Button
Now we need to wire up the button to the clearText: method. Hold
down the Control key and drag a connection from the button to
AppController. Remember you can release the Control key as soon as
you begin the drag.
In this case, though, you're starting the drag at the button and moving
towards AppController — the opposite of when we were connecting an
outlet.
When you release the mouse button, the Inspector window will
pop up with a list of possible action to connect the button to.
In some cases, you
may have to click the Target/Action tab to
see the available actions.
Make sure clearText: is selected then click Connect. This
method will be called every time the button is clicked.
When you're done, save the file and switch back to Xcode.
17
Try it Out
Back in Xcode, chose Build → Build and Run to try the
application out. You should now be able to type whatever text you want in
the application and clear it with the Clear Text button.
This may seem like a lot of work for such a simple feature, but
a lot of this was just basic set-up and fundamental concepts.
Adding additional code is now much easier.
18
Putting it All Together
Now that you've seen it all running, you might be curious about how the
pieces fit together. There are three basic pieces: the button, the
AppController instance, and the text view.
When the user clicks the button, it sends clearText: action to the
AppController, which uses
[textView setString:@" "] to clear the text view. This same process
is repeated every time the button is clicked.
You might wonder why the button doesn't clear the text view itself. In other
words, why involve AppController at all?
Model View Controller
In Cocoa, classes have specific responsibilities, which makes it
easier to create and change
complex applications. This design is called Model-View-Controller.
In CocoaNotepad, the View objects are the button and the text view.
The controller is AppController. We don't create model objects directly
in this tutorial, but you can think of them as the raw data, such as the
text in the text view.
By separating the View and Model objects, and using AppController to communicate
between the two, it's easier to add new data or new views without completely
rewriting the application.
Wrap Up
The goal of this tutorial is just to give you a taste of what Cocoa has
to offer. Even though we didn't write any code, we ended up with an application
which has some very sophisticated text handling. The Xcode project is contained
in the following zip file.
If you'd like to see more tutorials like this, please make a donation.
Please consider the download a
thank you gift for your contribution.
Read between the lines.