In this blog, we’ll explore simple animation of a UIView. We’ll draw an ellipse inside a square view to represent a ball, and animate the view as it travels down an incline. Let’s see how it works!
Start Xcode, select “Create a new Xcode project,” choose the Single View Application template, and click Next. Name the project InclinedPlane, and select options as shown here:
Click Next, choose a location to save the project, and click Create.
First, we’ll need to add a couple of UIView objects to our app, one to represent the ball, and the other to represent the inclined plane. Create these objects by navigating to File | New File… and choosing the Objective – C Class template. Click Next. In the “Subclass of” dropdown choose UIView. Name The class “Ball” and click Next. In the next window, click Create to save the class in the project location. Repeat the above steps to create another UIView class called “Plane.” When finished, the navigator should look something like this:
Open the Ball.m file and make the following changes:
@implementation Ball
– (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
}
return self;
}
– (void)drawRect:(CGRect)rect
{
// Drawing code
// Draw a red circle in the frame
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0);
CGContextAddEllipseInRect(context, rect);
CGContextFillPath(context);
}
@end
In initWithFrame: we are setting a clearColor background, which gives our Ball view a transparent background. In drawRect: we use the function CGContextAddEllipseInRect to create a red ellipse inside the view. This gives us a red ellipse inside a transparent rectangle. Since we’ll later constrain the ball’s width and height to the same value, the result will be a red circle representing a ball.
Open Plane.m and add this code:
@implementation Plane
– (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
}
return self;
}
– (void)drawRect:(CGRect)rect
{
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 1.0);
CGContextMoveToPoint(context, self.bounds.origin.x, self.bounds.origin.y);
CGContextAddLineToPoint(context, self.bounds.size.width, self.bounds.size.height);
CGContextAddLineToPoint(context, self.bounds.origin.x, self.bounds.size.height);
CGContextClosePath(context);
CGContextFillPath(context);
}
@end
Again, we’ve set the background color to transparent. This time in drawRect, we simply draw a filled triangle within a rectangle who’s properties we’ll set up when we instantiate the plane. The triangle is filled with a black color in this case.
Save both Ball.m and Plane.m. Now it’s time to implement these objects in the view controller. Open ViewController.h and add this code:
#import "Ball.h"
#import "Plane.h"
#define PLANE_HEIGHT 80
@interface ViewController : UIViewController
@property (nonatomic, strong) Ball *ball;
@property (nonatomic, strong) Plane *plane;
@property (nonatomic, strong) NSTimer *movementTimer;
@end
We import Ball and Plane, and also set a compile time constant PLANE_HEIGHT so that we can conveniently change the height (and therefore the slope) of the incline. Three properties are created, the Ball, the Plane, and an NSTimer object called movementTimer. We’ll use this timer to change the position of the ball every 0.0125 seconds.
Now open ViewController.m and make these changes:
@interface ViewController ()
@end
@implementation ViewController
@synthesize ball, plane;
@synthesize movementTimer;
– (Ball *)ball
{
if (!ball) {
ball = [[Ball alloc] init];
}
return ball;
}
– (Plane *)plane
{
if (!plane) {
plane = [[Plane alloc] init];
}
return plane;
}
– (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.plane.frame = CGRectMake(0, 200, self.view.bounds.size.width, PLANE_HEIGHT);
[self.view addSubview:self.plane];
CGFloat ballSize = 50;
CGFloat ballX, ballY;
ballX = 20;
ballY = [self verticalBallPositionWithX:ballX];
self.ball.frame = CGRectMake(ballX, ballY, ballSize, ballSize);
[self.view addSubview:self.ball];
self.movementTimer = [NSTimer scheduledTimerWithTimeInterval:0.0125
target:self
selector:@selector(moveBall)
userInfo:nil
repeats:YES];
}
– (void)moveBall
{
CGFloat ballSize = 50;
CGFloat ballX = self.ball.frame.origin.x;
ballX++;
CGFloat ballY = [self verticalBallPositionWithX:ballX];
self.ball.frame = CGRectMake(ballX, ballY, ballSize, ballSize);
}
– (CGFloat)verticalBallPositionWithX:(CGFloat)x
{
CGFloat ballSize = self.ball.bounds.size.width;
CGFloat ballY;
CGFloat ballRadius = ballSize/2;
// place the bottom of the ball image on the incline:
CGFloat planeX, planeY, planeSlope;
planeX = self.plane.frame.size.width;
planeY = self.plane.frame.size.height;
planeSlope = planeY / planeX;
ballY = (planeSlope * x + 200) – ballSize;
// adjust the ball down
CGFloat ballOffset = (planeY * ballRadius) / planeX;
ballY += ballOffset;
return ballY;
}
@end
As always, we’ve synthesized the properties created in the interface file. We also lazily instantiate Ball and Plane (by overriding their getters) so that when they are accessed, they will be created if they do not already exist.
In viewDidLoad: we first add the plane object as a subview of the main view. The rectangle to display the plane is controlled by the left and right edges of the main view, a hard coded vertical position (200), and PLANE_HEIGHT. Next, the size of the ball is set to 50, and the initial x position of the upper left corner of the ball is set to 20. The ball’s y position is found by calling verticalBallPositionWithX: on that x position. This private method returns the proper y position of the upper left corner of the ball taking the slope of the incline into account. Let’s examine this method in detail:
{
CGFloat ballSize = self.ball.bounds.size.width;
CGFloat ballY;
CGFloat ballRadius = ballSize/2;
// place the bottom of the ball image on the incline:
CGFloat planeX, planeY, planeSlope;
planeX = self.plane.frame.size.width;
planeY = self.plane.frame.size.height;
planeSlope = planeY / planeX;
ballX = self.ball.frame.origin.x;
ballX++;
ballY = (planeSlope * x + 200) – ballSize;
// adjust the ball down
CGFloat ballOffset = (planeY * ballRadius) / planeX;
ballY += ballOffset;
return ballY;
}
The ballSize is set by taking the width of the ball, and the radius of the ball is found by halving the ballSize. Next, we need to find the width and height of the plane, and from those values, calculate the slope of the incline. The slope is defined as the change in y over the change in x; we get this value in planeSlope. Next, we obtain the ball’s current x position, and increment it. We then calculate the y position by multiplying the slope by the x position, adding the fixed vertical offset of the plane (200), and subtracting ballSize from the result. At this point, the ball image is in this position:
We must now move the ball downward so that the bottom of the ball rests on the incline itself. Note that the ratio of y to x is the same as the ratio of y’ to x’. We know three of these values: y is the width of the Plane, x is the Plane’s height, and x’ is the radius of the ball. We find y’ (the distance to move the ball down) by taking (y / x’) * x. We add this value to the y position of the ball, and return it.
The timer object is set to fire the selector moveBall, which gets the current origin.x of the view and increments it before passing it to verticalBallPositionWithX: to find the correct y position. This method is called every 0.0125 seconds, and the ball appears to roll (it actually “slides”) down the slope.
By changing the value of PLANE_HEIGHT, the slope can be altered. Notice that this has no adverse effect on positioning the ball, as the slope is calculated at every increment of the timer. You might also try animating the slope angle to prove that the ball’s position is not affected by a change in the slope of the plane object. For a challenge, make the ball’s direction of movement change to reflect the angle of the slope… the ball should always move downhill. Have Fun!



