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.
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:
[c]#import
@interface Ball : NSObject {
CGPoint position;
CGPoint velocity;
CGFloat radius;
CGColorRef color;
}
@property CGPoint position;
@property CGPoint velocity;
@property CGFloat radius;
@property CGColorRef color;
@end[/c]
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:
[c]#import “Ball.h”
@implementation Ball
@synthesize position;
@synthesize velocity;
@synthesize radius;
@synthesize color;
@end[/c]
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.
[c]#import
#import “Ball.h”
@interface GravityTutorialViewController : UIViewController {
Ball *ball;
}
@end[/c]
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.
[c]- (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;
}[/c]
[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.
[c]- (void)dealloc {
[ball release];
[super dealloc];
}[/c]
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:
[c]#import
@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[/c]
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:
[c]#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[/c]
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.
[c]- (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];
}[/c]
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:
[c]#import
#import “Ball.h”
@interface GravityTutorialViewController : UIViewController {
Ball *ball;
}
– (void)onTimer;
@end[/c]
Then in the implementation .m file, we create the function and call ball’s update method:
[c]- (void)onTimer {
[ball update];
}[/c]
OK, start up your console (Cmd-Shift-R) and then Build and Go. You should get a bunch of lines being logged like so:
[c][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[/c]
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.
Wow Keith, add obj-c to your “ball tutorials” mantle. Great stuff, thank you!
I think in viewDidLoad you want ball = [[Ball alloc] init]; you need init to get an instance.
chandler, you may be right, but it seems to work fine without it. actually, in part 3 I add a custom init method.
chandler, I think alloc still gives you an instance, but init sets the object up internally. again, I get around to this in part 3, but you are right I should do it here.
http://developer.apple.com/DOCUMENTATION/Cocoa/Conceptual/ObjectiveC/Articles/chapter_4_section_2.html#//apple_ref/doc/uid/TP30001163-CH22-SW2
“Every class that declares instance variables must provide an init… method to initialize them.”
this is also very useful. http://developer.apple.com/DOCUMENTATION/Cocoa/Conceptual/ObjectiveC/Articles/chapter_4_section_3.html#//apple_ref/doc/uid/TP30001163-CH22-SW3 describing why init is important.
alloc allocates the memory, but doesn’t prepare the instance. I don’t think i’ve ever seen alloc without an init method before. you could also of course make an initWithPosition:(CGPoint)Pos velocity:(CGPoint)vel radius:(rad) color:(UIColor *)clr method to get something more akin to an ActionScript constructor. Awesome tutorial though, can’t wait to see what’s next!
Objective C is truly hideous.
chandler, yes, I’m planning on doing at least initWithColor and radius. I think it’s fine to have default position and velocity of 0, 0, as well as default gravity and bounce.
anyway, as i just quoted, “Every class that declares instance variables must provide an init… method to initialize them.â€
NSObject’s init does absolutely nothing. If you don’t have instance variables, or you want to leave them at 0 for default, then your init will be empty anyway. Of course, in this case, we really do need to define a color and radius as 0 is not very useful for either. At any rate, I know a lot more about it than I did yesterday and will faithfully provide inits for all my future classes.
Weird. Are you running this in the simulator or on the actual device? All I get is a grey screen … however, I can see the timer working, etc … so I must have missed a (critical) step … I have no compile time errors.
Scott, running on both.
Ah, disregard. This was creating the Ball object and timer only. I see it continues with part 2. 🙂
Gr8.. tutorial
I just found this website and I am hooked! A note on the NSLog Edit, I believe u are ok in calling the NSLog([[NSString alloc] initWithFormat:@”text @%”, dataName]);. I believe this is a Factory method that will be will be auto-released. But the Edit fix is still the best practice.
Oh, WOW, Helpfull information, THANKS. Greetings from Germany
Thanks again for the tutorial! I’m well on my way. I love how you put relations in my head from NSTimer to onEnterFrame and I see I can draw much like the API in AS.
Awesome tutorial! I was wondering what it would take to switch the draw ball to say a loaded .png image?
You should add a source file to each of you tutorials. It would be helpful. Also, when I do it, x goes up to 460, and y doesn’t move at all.
Referring to previous Comment—
I actually got it fixed, but you should still add a source file!
NSLog([[NSString alloc] initWithFormat:@”x: %f, y:%f”, position.x, position.y]);
this leaks memory, use the [NSString stringWithFormat:@”x: %f y: %f”, position.x, position.y ]; as it returns an autoreleased object.
Wow, this is amazing, great stuff you have here, save me lotsa time to study objective c, coming from AS3 background. Keep it up dude! Hope you’ll have many more
You sir, are a legend! I’ve done a lot of java, php, and extensive actionscript 3. Making a leap to Objective C was getting daunting based on readings of the docs on Apple’s developer site. I’ve only gone through part one of your tutorial, but already I understand much more in 10 minutes than I did after 2 hours of reading abstract “quick start” guides.
I know this is an old post but I just came across it. Love it. Question however.
After coding the viewDidLoad section in the ViewController.m file I am getting:
Assigning to ‘CGPoint’ (aka ‘struct’ ‘CGPoint’) from incompatible type ‘double’ for the line ball.radius = 20.0;
I also noticed in Ball.m I get Invalid operands to binary expression(‘CGFlot’ (aka ‘float’) and ‘CGPoint’ (aka ‘struct’ CGPoint’)) for every position and velocity line of code.
Ideas?