Gravity Tutorial for iPhone Part 1

Dec 27 2008 Published by under iPhone, Objective C

Send to Kindle

In order to not make this tutorial book length, I’m going to start with some assumptions.

1. That you have a Mac.
2. That you have XCode and the iPhone SDK installed.
3. That you have some familiarity with the environment and Objective C 2.0. Not much, but at least SOME. If you do a couple of Hello World tutorials on the net, you should be fine.

Also, see this note.

OK, let’s get started.

So, fire up XCode and start a new View-Based Application. Call it GravityTutorial or whatever.

The first thing we’ll need to do is make a class to hold the ball. Add a new file to the classes folder. NSObject subclass, name it Ball. This will create two files: Ball.h and Ball.m. The header and the implementation file.

We’ll add some properties to the ball for its position, velocity, radius, and color in Ball.h:

#import <foundation/Foundation.h>


@interface Ball : NSObject {
	CGPoint position;
	CGPoint velocity;
	CGFloat radius;
	CGColorRef color;
}

@property CGPoint position;
@property CGPoint velocity;
@property CGFloat radius;
@property CGColorRef color;

@end

First we declare them in the interface block, then we use the @property directive, which is one step in making automatic getter/setters. Since we are using Core Graphics for this, we’ll keep everything as CG types.

Now, the implementation in Ball.m:

#import "Ball.h"


@implementation Ball
@synthesize position;
@synthesize velocity;
@synthesize radius;
@synthesize color;

@end

The @synthesize directive is the second part of making the getter/setters. Congratulations. You made a custom class with public properties. Now we can make an instance of it and customize it.

If you named your project GravityTutorial, you’ll have a GravityTutorialViewController class, consisting again of an .h and an .m file. Let’s declare the ball in the header, GravityTutorialViewController.h – don’t forget to import the Ball.h file so the Ball class will be available here.

#import <uikit/UIKit.h>
#import "Ball.h"

@interface GravityTutorialViewController : UIViewController {
	Ball *ball;
}

@end

Note that ball is of type Ball *, or a pointer to a Ball object. I’m not going to go into pointer theory here, but in general, dynamically created class instances like this will be pointers. This mainly comes into play when creating them and casting them. You’ll get used to it. Since we are not using ball outside of the view controller class, we don’t have to create a property for it or synthesize it. It will just be a private variable.

Then we’ll create the instance in the implementation (.m) file. The best place to do this is in the viewDidLoad method. By the time this method is called, your application has generally done all the setup it needs to, and is ready for you to do your custom stuff. That method should already be in the template that was used to create the view controller, but commented out. Uncomment it and add the ball.

- (void)viewDidLoad {
	[super viewDidLoad];
	ball = [Ball alloc];
	ball.position = CGPointMake(100.0, 100.0);
	ball.velocity = CGPointMake(4.0, 3.0);
	ball.radius = 20.0;
	ball.color = [UIColor greenColor].CGColor;
}

[Ball alloc] is analogous to new Ball() in ActionScript. You are allocating memory and filling it with an instance of the class. Then you can set the position, velocity, radius and color.

[Edit - I just noticed this reference: http://developer.apple.com/DOCUMENTATION/Cocoa/Conceptual/ObjectiveC/Articles/chapter_4_section_2.html#//apple_ref/doc/uid/TP30001163-CH22-SW2, which says that we should be creating an init method to initialize instance variables, and should be calling that init method on creation. I do this in part 3 of this series. Just realize that it should really be done here.]

When you do create an object dynamically like this, you need to clean up after yourself. You’ve allocated the memory and the system will hold it for your object. When you are done with it, you need to tell the system that it’s ok to take back that memory. Near the bottom of the view controller class you’ll see a dealloc method. This is called when this class is destroyed, so that you can clean up anything you need to. Here we just need to call the release method of ball, which will release its memory.

- (void)dealloc {
	[ball release];
	[super dealloc];
}

You should be able to run this application at this point, and have it compile and run without any errors or warning. Clean them up if you see them. Of course you won’t see anything but a gray screen in the simulator, but that’s fine.

Next, we need to add a method to the Ball class so we can make it move. Back to Ball.h:

#import <foundation/Foundation.h>


@interface Ball : NSObject {
	CGPoint position;
	CGPoint velocity;
	CGFloat radius;
	CGColorRef color;
}

@property CGPoint position;
@property CGPoint velocity;
@property CGFloat radius;
@property CGColorRef color;

- (void)update;

@end

That second to last line declares the method, update, which returns void. the “-” means it’s a public instance method. “+” would be a static (class) method.

Now the implementation in Ball.m:

#import "Ball.h"


@implementation Ball
@synthesize position;
@synthesize velocity;
@synthesize radius;
@synthesize color;

- (void)update {
	position.x += velocity.x;
	position.y += velocity.y;

	if(position.x + radius > 320.0) {
		position.x = 320.0 - radius;
		velocity.x *= -1.0;
	}
	else if(position.x - radius < 0.0) {
		position.x = radius;
		velocity.x *= -1.0;
	}

	if(position.y + radius > 460.0) {
		position.y = 460.0 - radius;
		velocity.y *= -1.0;
	}
	else if(position.y - radius < 0.0) {
		position.y = radius;
		velocity.y *= -1.0;
	}
	NSLog([[NSString alloc] initWithFormat:@"x: %f, y:%f", position.x, position.y]);
}

@end

This is basic velocity and bouncing code. Add the velocity to the position, check the boundaries, set to the edge of the boundary and reverse direction. The iPhone's screen is 320x480, minus a 20 pixel tall status bar, so 320x460.

The last line is a not-so-user-friendly version of trace(). NSLog sends a log message, but you need to allocate an NSString and format it with the numbers you want to trace. If you've ever used printf, that will look fairly familiar.

[Edit - It's been pointed out to me by a couple of people that that log line is leaking memory. I'm allocating memory for a string and never releasing it. Apparently, with NSLog, you can just do this:

NSLog(@"x: %f, y:%f", position.x, position.y);

and it does the formatting and takes care of memory for you. Good to know. You should replace that log line with this one.]

Test again just to make sure it compiles. Fix any errors.

Now let's make it move. Well, the theoretical ball will move. Rendering it to screen will come later. That's why we're logging the position, so you know it's happening.

Objective C doesn't have EnterFrame events, so we'll use a timer. An NSTimer to be exact. We'll do that in the view controller, again in the viewDidLoad method.

- (void)viewDidLoad {
	[super viewDidLoad];
	ball = [Ball alloc];
	ball.position = CGPointMake(100.0, 100.0);
	ball.velocity = CGPointMake(4.0, 3.0);
	ball.radius = 20.0;
	ball.color = [UIColor greenColor].CGColor;

	[NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:@selector(onTimer) userInfo:nil repeats:YES];
}

Here we are calling a static method of the NSTimer class called scheduledTimerWithTimeInterval. Objective C doesn't skimp on method names. The interval we are setting to 1.0/30.0 or 1/30th of a second. The target is self, which is like "this" in ActionScript. The selector is the method that you want to call on the target when the timer fires. We want to call the onTimer method. I'm not sure about the @selector(onTimer) syntax, but it some how formats the function name into something that can be used as a callback for the timer. User info is nil, which is like null, and yes, we want it to repeat. YES and NO are true and false.

Now of course we need that onTimer function. First declare it in the .h file:

#import <uikit/UIKit.h>
#import "Ball.h"

@interface GravityTutorialViewController : UIViewController {
	Ball *ball;
}

- (void)onTimer;

@end

Then in the implementation .m file, we create the function and call ball's update method:

- (void)onTimer {
	[ball update];
}

OK, start up your console (Cmd-Shift-R) and then Build and Go. You should get a bunch of lines being logged like so:

[Session started at 2008-12-27 11:27:56 -0500.]
2008-12-27 11:27:58.521 GravityTutorial[62409:20b] x: 104.000000, y:103.000000
2008-12-27 11:27:58.554 GravityTutorial[62409:20b] x: 108.000000, y:106.000000
2008-12-27 11:27:58.587 GravityTutorial[62409:20b] x: 112.000000, y:109.000000
2008-12-27 11:27:58.621 GravityTutorial[62409:20b] x: 116.000000, y:112.000000
2008-12-27 11:27:58.654 GravityTutorial[62409:20b] x: 120.000000, y:115.000000

As you can see, the x and y values are changing. When x reaches 320 or y reaches 460, you'll see them go in the opposite direction.

Well done. You've made a custom object, made an instance of it, assigned values to its properties, called a method on it, and used a timer to "animate" it.

Cocoa is very much MVC oriented. We've just create the ball model, and customized the controller. In part 2, we'll create the view, so we can actually see something moving.

Send to Kindle

40 responses so far. Comments will be closed after post is one year old.