Gravity Tutorial for iPhone part 4

I have to admit, I’m writing these things for purely selfish reasons. Same reason I write books. You learn WAY more by teaching. If you think you are getting some knowledge from reading these, realize that I am getting SO much more knowledge and understanding from writing them. 🙂

Now let’s use one of the really neat features of the iPhone, that lovely touch screen. There are three methods we can add to our view controller to listen for touch events: touchesBegan, touchesMoved, and touchesEnded. It inherits these from the UIResponder class, so you don’t need to declare them, just implement them. Note that UIView is a UIResponder too, so you could put touches methods in there, but it makes more sense in this app to have it in the view controller, where we have a constant reference to the ball. To start with, add these method to GravityTutorialViewController.m:

[c]- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@”touches began”);
}

– (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@”touches moved”);
}

– (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@”touches ended”);
}[/c]

Open up your console and run the app and see that it does indeed respond to these actions.

You see that each method gets passed an NSSet of touches as a first parameter. An NSSet is “an unordered collection of distinct elements”. Realize that the touch screen is multitouch, so a touch event may have several simultaneous touches going on – one for each finger. Here of course, we’re really only concerned with a single touch. We get that by calling the anyObject method of the set. This will give us a single UITouch object. We can then call the locationInView method of the UITouch object to get a CGPoint showing the x, y location of that touch.

We’ll store this point in a class variable, but let’s actually make two, one for the last touch, and one for the current touch, so we can track movement and velocity. Declare these two in the view controller’s .h file:

[c]#import
#import “Ball.h”

@interface GravityTutorialViewController : UIViewController {
Ball *ball;
CGPoint lastTouch;
CGPoint currentTouch;
}

– (void)onTimer;

@end[/c]

We don’t need to make properties or synthesize these, as they are only used in the class. Now, we can grab the current touch location in each of the touch methods. Actually, we don’t really need it in touchesEnded, as you’ll see in a moment.

[c]- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
currentTouch = [touch locationInView:self.view];
}

– (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
currentTouch = [touch locationInView:self.view];
}[/c]

First off, in touchesBegan, we need to see whether or not the user has touched the ball. Unlike in ActionScript where you have handy mouse down and mouse up and click events that tell you whether or not you clicked on something, here you have to do it all by hand. We’ll just check the currentTouch location’s distance from the ball’s location. If that is less than the ball’s radius, the user touched the ball.

Oddly enough, there doesn’t even seem to be a built in distance method here, so we have to roll our own for that too. No problem though, we know Pythagorus quite well, right? A squared plus B squared and all that.

[c]- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
currentTouch = [touch locationInView:self.view];
CGFloat dx = currentTouch.x – ball.position.x;
CGFloat dy = currentTouch.y – ball.position.y;
CGFloat dist = sqrt(dx * dx + dy * dy);
if(dist < ball.radius) { ball.velocity = CGPointMake(0.0, 0.0); ball.dragging = YES; } lastTouch = currentTouch; }[/c] So, if the user has touched the ball, what do we do? First of all, we kill the velocity by setting it to 0.0. That way if he touches and releases it without moving, it will just drop to the floor, rather than continue on the way it was going. Then we set the ball's dragging property to YES (true). I know, that property doesn't exist yet, but we'll be adding it soon. But first, let's wrap up the touch stuff. Finally, in this method, we assign the currentTouch to lastTouch so we know how far it moved, if and when it moves. In touchesMoved, we need to move the ball to the current touch. Simple enough. We also need to update the velocity by subtracting the lastTouch from the currentTouch. In other words, how far did the touch move since last time? Well that's the ball's current velocity. If the user releases it, that's how fast it should go, and in what direction. [c]- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; currentTouch = [touch locationInView:self.view]; ball.position = currentTouch; ball.velocity = CGPointMake(currentTouch.x - lastTouch.x, currentTouch.y - lastTouch.y); lastTouch = currentTouch; }[/c] Finally, in touchesEnded, we just tell the ball we are not dragging it anymore. [c]- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { ball.dragging = NO; }[/c] Now, onto this dragging stuff. We'll declare a BOOL variable in Ball.h and make it a property. [c]@interface Ball : NSObject { CGPoint position; CGPoint velocity; CGFloat radius; CGColorRef color; CGFloat bounce; CGFloat gravity; BOOL dragging; } @property CGPoint position; @property CGPoint velocity; @property CGFloat radius; @property CGColorRef color; @property CGFloat bounce; @property CGFloat gravity; @property BOOL dragging; - (void)update; - (CGRect)getRect; @end[/c] And synthesize it in Ball.m: [c]#import "Ball.h" @implementation Ball @synthesize position; @synthesize velocity; @synthesize radius; @synthesize color; @synthesize bounce; @synthesize gravity; @synthesize dragging; ...[/c] Finally, in the update method, we check to see if we are dragging the ball or not. If so, we just return. We don't want to do all that gravity and bouncing nonsense. We just want to let the user drag the ball. [c]- (void)update { if(dragging) return; velocity.y += gravity; position.x += velocity.x; position.y += velocity.y; if(position.x + radius > 320.0) {
…[/c]

That if statement up at the top is the only change to this method.

Now, we are about half way there. Oh, no, wait, we are done! Yup. Run the application (I still want to say “Test Movie” but I’m working on it) and you should have a dragable, throwable, bouncable, thoroughly lovable ball on your iPhone, iPod, or at very least, simulator.

Next up… accelerometer!

This entry was posted in iPhone, Objective C. Bookmark the permalink.

13 Responses to Gravity Tutorial for iPhone part 4

  1. Willem says:

    Thanks for this. Very informative.

  2. Bob says:

    I found a bug, or should i say something that might be improved in the code:

    the touchesMoved method should check for the ball.dragging or it will move the ball even though it hasn’t been pressed:

    – (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    currentTouch = [touch locationInView:self.view];
    if( ball.dragging ) {
    ball.position = currentTouch;
    ball.velocity = CGPointMake(currentTouch.x – lastTouch.x , currentTouch.y – lastTouch.y );
    }
    lastTouch = currentTouch;
    }

  3. yay. Draggable gravity ball with touch access = success!

  4. five March says:

    Nice tutorial, great to start, then i wanted to have two balls… but BallView, being like a DisplayObject in CS3 supports only one drawRect(?),
    (like you said, indeed, doing it yourself means learning)
    so creating in GravityTutorialViewController.h :

    IBOutlet UIImageView *moon;
    IBOutlet UIImageView *sun;

    and connecting the Outlets in the interface builder to the File’s Owner
    and doing
    – (void)onTimer {

    [ball update];
    moon.center = ball.position; //the images use the ball positions
    [ball2 update];
    sun.center = ball2.position;

    [(BallView *)self.view refresh:ball];

    }
    in GravityTutorialViewController.m
    (having init’s for both balls, with different parameters)
    so this gave two nice jumping balls
    (the balls being images imported in the Image View objects in the interface.)

    i like the images more then the circles,
    trying to do the same trick with UIView objects in the interface builder didn’t work like the UIImageView objects, i’ll have to look into that more in detail

    probably the UIView BallView is still drawing the circles underneath the images…
    🙂 …..i’ll have to organize this

  5. snowytree says:

    Great tutorial, great site! Thanks for the clear and simple instructions.

  6. George says:

    I’m Sooooo glad someone could put this in terms of actionscript, even for people without actionscript knowlage i’d have to say it’s one of the best tutorials out there. Thanks a million =].

    Very small improvement, if you don’t do what bob said and you’d prefer to have the throw action occur everytime you touch the screen no matter where (like in the code) then add ball.dragging = YES; inside the touchesMoved event, this means that if you throw the ball starting a a location outside the radius of the ball, it won’t drop if you keep the touch still

  7. Krish says:

    how to use a pagecontrol and drag this ball from the first page to the second page

  8. Geoff says:

    Keith!

    These tutorials are great. You should include a link to the next one at the bottom of each one, though, so it’s easier to go through them in order!

  9. Alex says:

    Good tutorial, doesn’t actually work due to the “-[UIView refresh:]: unrecognized selector sent to instance” problem but non the less very informative. I’ve absorbed loads just from following the code 🙂 I have a feeling though that with just a very basic understanding of view and object typing I could have fixed the error I mentioned above (and a few others did before me). So that’s next for me.

  10. Alex says:

    Sorry, everything’s working perfectly fine. As the idiot I am I skipped certain parts of the text which explained perfectly well that we needed to change the view used by the controller to our new BallView. As I hadn’t done that, the type of view I was trying to refresh differed from the default one! So in a nutshell I was trying to send a UIView to a method that takes a BallView, pffft.

    Great tutorial, and sorry again for bashing your fully functional code 🙂

  11. Lillarcor says:

    Thank you very much for the tutorial 🙂

    Coming from AS3 you have made the transition to Objective C and iPhone development a lot easier for me…

  12. Tom Elliott says:

    So far so good, thanks for the great set of tutorials and references to AS3 – some of it’s now sinking in! I’m running virtual Mac OSX on my PC so I can learn iPhone Dev.

    Cheers

    Tom

  13. yossi says:

    hello,

    really understandable and very very good tutorial.

    I have a question, I want that when I click on the ball (beganTouch), the ball will jump up a little bit and will start falling down again, when I click again he will jump again and than again, start falling down. means that only when I tap on it, he jumps, without the dragging function.

    thanks alot!

Leave a Reply