In part 1, we created a Ball class which contained position, velocity, radius, and a color. And we set up a timer to update the ball’s position based on its velocity and bounce off the walls.
Now let’s render that ball to the screen.
Rendering is done in a view class, which is of type UIView or a sublcass of UIView. when you create a View-Based application, XCode automatically sets up a generic view of type UIView. Often this is fine, but since we need to do some drawing with code, we need to have a custom view class.
So create a new file with the UIView subclass template. Name it BallView. This will create BallView.h and BallView.m files.
We’re going to need a method to tell the view to refresh, and something to tell it where to draw the ball. Here’s the header:
[c]#import
@interface BallView : UIView {
CGRect ballRect;
}
– (void)refresh:(CGRect)rect;
@end[/c]
Here we have a CGRect which is a data class that holds a rectangle definition. That’s a private variable. And we have a refresh method that takes a CGRect as a parameter. Funky syntax there if you are coming from an ActionScript world, but you’ll learn to love it. 🙂
In the BallView.m file, we’ll implement the refresh method:
[c]- (void)refresh:(CGRect)rect {
ballRect = rect;
[self setNeedsDisplay];
}[/c]
This assigns the rect parameter to the ballRect class variable, and tells the view that it needs to re-display itself. When this happens, the drawRect method of the view will run. That is already defined for you, with a space to add your code.
Think of a view like an ActionScript DisplayObject. In ActionScript, you need to get the graphics object of the display object to do any drawing. In UIView you need to get a Core Graphics context. That’s done with the UIGraphicsGetCurrentContext method, which returns an instance of CGContextRef. Then you set the line width, line color, fill if necessary, add some points, lines, shapes, or whatever to a path, then draw or stroke the path. I won’t go into it in detail. I’m sure when you see the code, you’ll get the idea pretty readily.
[c]- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextAddEllipseInRect(context, ballRect);
CGContextStrokePath(context);
}[/c]
Dig through the references for anything that begins with CGContext to get a feel for the different drawing API commands. I skimped here and just hard coded green as the drawing color. This really should be passed in with the refresh method. Or maybe even pass in the whole ball instance, but this works fine for now. As I said, we get the context, set the stroke color and line width, add an ellipse using the ballRect that we just saved and stroke the generated path.
OK. Our BallView class is complete. Now we need to tell XCode to use this class instead of the default one provided in the template. To do this, we need to go into Interface Builder. OK, we don’t NEED to. There are ways to do it with code too. But in this tutorial, that’s how we are going to do it.
Think of Interface Builder as Flex Builder’s design view. IB generates .xib files which are actually xml files, though I’m not sure anyone actually edits xib files by hand like they do with mxml files.
In the resources folder of your project, you should see a GravityTutorialViewController.xib file. Double click on that and IB will launch.
In the Document window you should see three icons – File’s owner, First Responder, and View. I’ll leave it to you to learn about the first two. Here were are concerned with the View. Click on it and open up the Identity Inspector window. At the top of that you should see a Class dropdown with UIView selected. This is telling you that the default view used for this project is a generic instance of the UIView class. We want it to use BallView instead. Simple enough. Just click on the dropdown and change the class to BallView. The icon label should now say “Ball View”. Good. Now you might want to open the Attributes Inspector window and change the background color to black or something other than gray.
Save the .xib file, and exit IB if you want, because we are done with it.
Back to XCode. You should be able to run the app again with the same results as before. You still won’t see anything, but it should compile and run with no errors or warnings. And if you have the console open, you should still get the log statements showing position.
Now, back in our GravityTutorialViewController.m file, in the onTimer method, we need to tell the view to refresh, passing in the rectangle of the ball’s location and size.
First we’ll create the rectangle, which is based on the ball’s position and radius. Then we need to call refresh on the view. The view controller has a view property that refers to the view, but the problem is that it is typed as a pointer to UIView. So we’ll need to cast it to a pointer to BallView or we’ll get a compile error when we try to call the refresh method.
[c]- (void)onTimer {
[ball update];
CGRect rect = CGRectMake(ball.position.x – ball.radius, ball.position.y – ball.radius, ball.radius * 2.0, ball.radius * 2.0);
[(BallView *)self.view refresh:rect];
}[/c]
Also, this is the first time we’ve used the BallView class in this class, so we’ll need to import it at the top of the class:
[c]#import “GravityTutorialViewController.h”
#import “BallView.h”
@implementation GravityTutorialViewController
…[/c]
You can remove the NSLog statement now, as you should actually have a real moving ball now. Build and go, and behold your bouncing ball!
In Part 3, we’ll do some enhancements and even add gravity!
Hey Keith! This tutorial is great for an old as developer! 🙂 Although i get myself some errors while compiling that i’m not sure how to debug:
Undefined symbols:
“_CGContextAddEllipseInRect”, referenced from:
-[BallView drawRect:] in BallView.o
“_CGContextSetLineWidth”, referenced from:
-[BallView drawRect:] in BallView.o
“_CGContextStrokePath”, referenced from:
-[BallView drawRect:] in BallView.o
“_CGContextSetStrokeColorWithColor”, referenced from:
-[BallView drawRect:] in BallView.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
Does it miss some imports or what symbols is it talking about?
Thanks for giving us a kick in the right direction! 😉
/bob
Nvm, i missed your notice in the beginning, after adding CoreGraphics.framework it compiles nicely!
After crashing XCode a few times … Bouncing Ball success!
The last step when I’m supposed to change UIView to BallView, BallView is not in the dropdown and if I type it in, it just disappears after I save. I know I’m missing something, help?
Nevermind, I created the BallView files as NSObject subclass instead of View
The edges of the circle blur when in motion. How would you go about making it smooth and seamless?
Awesome tutorial Keith. Thanks!
in this line the following error occours:
[(BallView *)self.view refresh:rect];
2009-07-15 14:36:46.751 BallDemo[2697:20b] x: 104.000000, y: 103.000000
2009-07-15 14:36:46.753 BallDemo[2697:20b] *** -[MainView refresh:]: unrecognized selector sent to instance 0xd29ac0
2009-07-15 14:36:46.754 BallDemo[2697:20b] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘*** -[MainView refresh:]: unrecognized selector sent to instance 0xd29ac0’
what 2 do now?
Yah, I am also getting the same errors as Benedikt…. whats up with that?
I get a warning that says ‘NSTimer’ may not respond to ‘+scheduledTimerWithTimeInterval: etc. in the viewController.m. When I launch the simulator, it just shuts opens and shuts down again.
here is my code. It works fine.
[NSTimer scheduledTimerWithTimeInterval:1.0 / 20.0
target:self
selector:@selector(onTimer)
userInfo:nil
repeats:YES];
Nice tutorial!
I’m trying to adapt your tutorial whilst writing and do it without a nib file. However, the ball is not refreshing properly. It leaves a trail of balls behind it. Any tips on removing this?
Thanks!
@oliver
pass this message to your uiview container
setClearsContextBeforeDrawing:YES
Guys, you can download the files from here:
http://www.andreipotorac.com/GravityTutorial.zip
There were a few issues in the code, which needed a little tweaking, but it works very nice!
Flash Developer learning Obj-C as well. 🙂
re. the ball leaving trails when creating view without NIB
>pass this message to your uiview container
>setClearsContextBeforeDrawing:YES
I placed this in my viewDidLoad, where viewPtr is my BallView.
[viewPtr setClearsContextBeforeDrawing:YES];
it makes no difference and the documentation says that the default is YES, anyway.
I’m actually beginning to learn Obj-C; had some trouble because of the newer versions of Xcode, but I got it working and this helped me around! Thanks for this tutorial!
Can You please tell me
which line in this code sets colour of the ball.
because i am being unable to get green coloured ball.
it always takes colour of the background
i am using Xcode 4.6 and iOS 6.1 simulator