MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 See this if you're having trouble printing code examples


Programming With Cocoa

Animating Graphics in Cocoa, Part 1

01/04/2002

In today’s column I'm going to discuss animating graphics in Cocoa. I'll cover several new classes, including NSAffineTransform, NSTimer, and NSThread, as well as a new method of an old class, NSBezierPath.

Today's project presents us with nothing more than a ball bouncing around inside of our application’s window, which we will later expand upon with some user interface controls.

Before delving into the article, I need to go through the standard motions of creating the project and setting up the interface. First, create a new project named Animation. (Pretty original, eh?) To this project, add an Objective-C subclass of NSView named AnimationView by selecting New File… from the File menu and choose 'Objective-C NSView subclass' from the New File dialog.

Now to set up the interface. This is done in the same fashion we have learned about in the last two columns. Drag the file AnimationView.h from Project Builder into the nib window in Interface Builder; this will add AnimationView to the list of available classes in IB.

Next, drag onto the window an NSView container. From the Info panel, change the class of the view container from NSView to AnimationView. If this procedure is unfamiliar to you, refer back to the first graphics column. Save your work, and it's back to Project Builder.

Animation in a Nutshell

Learning CocoaLearning Cocoa
By Apple Computer, Inc.
Table of Contents
Index
Sample Chapter
Full Description
Read Online -- Safari

Animation is really nothing more than a series of images (frames) in which there are subtle changes to those images, such as position movement between successive frames. Our job as programmers is to set up a system where we redraw our view repeatedly, while simultaneously effecting some small, incremental changes to our image. In the case of our bouncing ball application, we want to repeatedly call a method, called stepAnimation:, that moves the ball a little bit in some direction, and then tells the view to redraw itself.

To accomplish this we need two things: a way to time the animation, and a way to move the shapes. We will learn in a moment two different ways we can time the animation, as well as how we can easily move bezier paths around in our view. The first timing technique will use the class NSTimer, while the second technique will make use of the class NSThread. A class called NSAffineTransform provides us with the ability to move bezier paths. With that, let's look at the first of these classes, NSTimer.

NSTimer

An NSTimer object, or more succinctly, a timer, is an object that waits some amount of time and then sends some pre-determined message to a pre-determined object. NSTimer is a bigger beast than I’m letting on here, so read up on the class documentation in the Foundation reference.

A timer can be set to fire once, or it can be set to repeat on some time interval. What we'll do is create a timer object that fires every 0.04 seconds, which will give us an animation with approximately 24 frames per second. The method that is called when the timer is fired is a method called stepAnimation:, which we will add to our NSView subclass. Creating a timer is easy to do in the initialization of our view. The method we use to create a new timer is –scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:. We will modify the default initWithFrame: method of AnimationView to create a timer object as follows:



- (id)initWithFrame:(NSRect)frame {
    NSTimer *timer;

    self = [super initWithFrame:frame];
    if (self) {
        timer = [NSTimer scheduledTimerWithTimeInterval:0.04 
 target:self 
 selector:@selector(stepAnimation:) 
 userInfo:nil 
 repeats:YES];
    }
    return self;
}

What we did here was to set the time interval to fire the timer every 0.04 seconds; the target object of the message sent by the timer is the view itself, and the method to be invoked is stepAnimation:. The method that is invoked by the timer must have exactly one argument that is type NSTimer *.

When a timer is fired, the message to the target object contains an argument that is the timer that sent off the message. In this way, we can get some information about the timer. The userInfo argument allows us to send any piece of information along with the timer object, which can then be retrieved by sending the timer (which is accessed via the argument of stepAnimation:) a -userInfo message. Finally, we tell the timer that it should reschedule itself everytime it is fired; i.e., repeat. With this, we have set up a first crucial component of making the animation work. Now we go onto to take a look at the stepAnimation: method and how we move things around.

NSAffineTransform

The first time I made a Cocoa application that simulated a ball bouncing around in a box, I set things up in such a way that in each step of the animation, I incrementally changed the position of an NSRect used to define the bounds of a circular bezier path by monkeying around with the individual elements of the NSRect struct. In the displayRect: method of my view I then created a new bezier path using that NSRect as a guide, and drew it. This is an effective, yet crude way of doing things that requires us to poke around deep in the guts of the elements of the graphic.

There exists a higher-level way of doing such a thing that doesn’t require us to mess with the internals of the shapes, and that is with the class NSAffineTransform. "What exactly is an affine transform?" you ask. In general, a transformation is any operation that changes a shape, or more specifically, maps points in one coordinate system to points in another set of coordinates (but that’s a bunch of mumbo jumbo that a lot of us don’t really care about right now).

An affine transform is a special type of transformation in which lines are guaranteed to be parallel after a transformation operation (if they were parallel to start with), but the length of lines does not need to stay the same between transformations, nor do angles between lines need to remain the same (more mumbo jumbo).

So what does this leave us with? NSAffineTransform gives us the standard gamut of affine transformations: translation, rotation, and scaling -- useful, practical stuff. In our application, we will use a translation transformation, which is simply an operation that slides a shape around in the coordinate plane. NSAffineTransform is another beast of a class loaded with all sorts of good stuff, so read up on it too.

Comment on this articleToday's tutorial was a hefty one. How's it working for you?
Post your comments

The way we use an affine transform is to first create an instance of NSAffineTransform. A new instance of NSAffineTransform won’t do anything by itself, so we have to indicate what type of transformation we want this object to accomplish. We need a translation, so we send a –translateXBy:yBy: message to our new affine transform object. This method’s arguments are two floats indicating how far in either direction we want the transformation to translate objects. The class documentation to NSAffineTransform outlines how you do rotations and scaling, so if those operations are appropriate to your needs, check it all out.

In our application, we’ll make our affine transform an object instance variable so that it can be accessed from any method of the class. This will be useful later, when we want to change the transformation. Add to the header file a declaration for an affine transform as follows:

NSAffineTransform *at;

In the if statement of initWithFrame, just before the timer initialization, add the following line of code:

at = [[NSAffineTransform transform] retain];

We must use retain here, since transform is a convenience constructor, which returns an autoreleased object by default.

One last bit of initialization to be done is to create a translation with our affine transform. That is, when our animation first starts, how do we want the ball to move? As was mentioned above, the method we use to create a translation is –translateXBy:yBy:, which takes two arguments: the distances we want to move whatever object we operate on in the x and y directions. So, if we wanted to create a translation that moves the ball vertically one unit and to the right one unit, we would make both arguments 1.

Also in Programming with Cocoa

Understanding the NSTableView Class

Inside StYNCies, Part 2

Inside StYNCies

Build an eDoc Reader for Your iPod, Part 3

We can apply a transformation to a bezier path in one of two ways. One way is to call a method of NSBezierPath called –transformUsingAffineTransform:, which changes the bezier path according to the transformation given in the argument. There is no return object in this method. The other method we can use comes to us from NSAffineTransform, which is –transformBezierPath:. This method takes a bezier path as an argument, and returns a new bezier path that is the transformation of the original, leaving the original bezier path in its original state. In our animation, we’ll use the first method.

At this point, we want to declare two more instance variables in the class interface file. They are variables which we will use to designate the incremental x and y movements of our ball. In the header file add the following instance variable declaration code:

float dx, dy;

The d stands for delta, as in delta-x and delta-y -- changes in those dimensions. Now, in the initialization code of the view, again, we will give these variables values that represent the initial motion of the ball, and then use these variables in defining an initial translation. So to move our ball initially in the motion of one unit up and one unit to the right, we add this code after the affine transform creation line:


dx = 1;
dy = 1;
[at translateXBy:dx yBy:dy];

With this, we’re almost done with the initialization. We need simply to instantiate a circular bezier path for our ball, and we’re ready to move onto the meat of the animation (simple as it will be). Declare one more instance variable in the interface file:

NSBezierPath *ball;

And initialize it in initWithFrame as follows:


NSRect ballRect; //defined at the beginning of this method

ballRect = NSMakeRect(10, 10, 10, 10);
ball = [[NSBezierPath bezierPathWithOvalInRect:ballRect] retain];

With this, you should have an initialization method that looks like the following:


- (id)initWithFrame:(NSRect)frame {
    NSRect ballRect;
    
    self = [super initWithFrame:frame];
    if (self) {
	dx = 1;
	dy = 1;
	
	at = [[NSAffineTransform transform] retain];
	[at translateXBy:dx yBy:dy];

	ballRect = NSMakeRect(10, 10, 10, 10);
	ball = [[NSBezierPath bezierPathWithOvalInRect:ballRect] retain];

	[NSTimer scheduledTimerWithTimeInterval:0.04
		 target:self 
		 selector:@selector(stepAnimation:) 
		 userInfo:nil 
		 repeats:YES];
    }
    
    return self;
}

Before we move on to the method that does the animation, let's set up -drawRect: to draw our path. There is nothing out of the ordinary here; simply pick a color and fill in the ball bezier path (or you could draw an outline of the ball using –stroke): - (void)drawRect:(NSRect)rect { [[NSColor redColor] set]; [ball fill]; }

Now let's get things moving with the animation code.

The Animation

Recall that when we set up our timer, we arranged it such that a method of our custom NSView called stepAnimation was invoked every time the timer fired. The contents of this method are the subject of this section. At this point in our application’s implementation, we have created a bezier path that is the ball we wish to animate moving around. We have also created a timer object which will provide us a way of timing the animation. Most importantly, we have created an NSAffineTransform object that will be used to move the ball around.

The last thing we have to do here is to move the ball every time the method stepAnimation: is called by the timer. What we want to do is apply the affine transformation to the bezier path. After we have done that, we simply tell the view that the contents of the view have changed, and that it needs to be redrawn. To accomplish these things we require only two lines of code. Add to the class implementation the following definition of the method stepAnimation::


- (void)stepAnimation:(NSTimer *)timer
{
    [ball transformUsingAffineTransform:at];
    [self setNeedsDisplay:YES];
}

Here, we change the bezier path into the translated path using the method transformUsingAffineTransform:, the argument of which is our transformation, at. What this method does is actually change the bezier path according to the transformation indicated in the argument. Thus, for the translation of x by 1 and y by 1 that we discussed earlier, 1 would be added to each coordinate of every point in the bezier path. In effect, the ball is moved up and to the right by 45 degrees and a distance of approximately 1.4 pixels. After changing the path, we tell the view that it is time to redraw, and the ball is drawn at the new location. This operation is repeated continuously, and we experience the sight of a ball moving across the screen.

With this code, you should be ready to compile and go and see the fruits of your labor. I imagine, however, that your enchantment will persist as long as it takes for the ball to disappear from the view. To remedy this, I offer you in the next section a way to make the ball bounce of the sides of the view bounds and remain in view.

Bouncing

To implement bouncing behavior we don’t have to do much. All we need to do is to check if the ball is crossing the bounds of the view, and if so, change the sign of dx or dy to create the appropriate horizontal or vertical bounce. What we’ll first do is make our code clean from the start by creating a method called checkCollision, which will contain the logic to enable bouncing. This method is called in the stepAnimation method between the two existing lines:


- (void)stepAnimation:(NSTimer *)timer
{
    [ball transformUsingAffineTransform:at];
    [self checkCollision];
    [self setNeedsDisplay:YES];
}

So what do we do in -checkCollision? Simple: the ball has a bounding NSRect that can be accessed by sending a -bounds message to the bezier path. This method returns an NSRect that provides us with the location and size of the ball in our view -— both crucial pieces of information. The idea is to check whether a the top edge of the ball is above the top edge of the view, or if the bottom edge of the ball is below the bottom of the view. If either of these conditions is true, we reverse the direction of the y translation; that is, we multiply dy by –1. Thus, if the ball was going up, it would go down when it hits the top. If it was going down, it would go up when it hits the bottom.

Similarly, we do the same for the x direction of motion. If the left edge of the ball goes beyond the left edge of the view, we change the sign on dx. If the right edge of the ball goes beyond right edge of the view, again we change the sign of dx. In all of these cases, we must take a further step: we must change the affine transformation to reflect the new values of dx and dy we get as a result of bouncing. This is easily done by releasing the object previously assigned to at, creating a new affine transformation, and then creating a translation transformation.

Let's look at our method checkCollission to see how all of this fits together.


- (void)checkCollision
{
    NSRect ballRect = [ball bounds];
    NSRect viewRect = [self bounds];

    if ( ballRect.origin.y < viewRect.origin.y || 
	 (ballRect.origin.y + ballRect.size.height) > viewRect.size.height ) {
	dy = -dy;

	[at release];
	at = [[NSAffineTransform transform] retain];
	[at translateXBy:dx yBy:dy];
    } else if ( ballRect.origin.x < viewRect.origin.x ||
                (ballRect.origin.x + ballRect.size.width) > viewRect.size.width ) {
	dx = -dx;

	[at release];
	at = [[NSAffineTransform transform] retain];
	[at translateXBy:dx yBy:dy];
    }
}

You see in the first two lines that we access the rectangles that define both the bounds of the ball and the bounds of the view. Information about the location and sizes of these two rectangles will prove most important to accomplishing our task. The rest of the method is composed of two logically identical if statements.

In the first, we check to see if the bottom of the ball is below the bottom of the view or if the top edge of the ball is above the top of the view. If so, we set dy equal to negative dy, release at, create a new affine transform, and use our new value of dy to define a new translation for at. The second if statement is no different, except that it applies in the horizontal direction.

With this simple method, we are able to keep the ball in the view indefinitely, and you can stare in wonder at the ball bouncing in the view. Compile it and check it out! If you're interested, you can download my project folder here.

Issues of User Interaction

What we're going to do now is add two sliders to our application’s interface. One slider controls the speed in the x direction, and the other the speed in the y direction. Effectively, the speed in the x and y directions is controlled by changing the magnitude of dx and dy, within the AnimationView object. Rather than having AnimationView handle the action methods of the sliders, we will implement a controller class to interface the AnimationView with the slider controls. Let's go into Interface Builder and add these sliders and set up the controller object.

In Interface Builder

As it is, the AnimationView container takes up the entirety of the window. Without changing the size of this container, make the window a little bigger vertically to allow room for some sliders. Add the sliders and some labels as I have done in the image below.

AnimationView

I have configured the sliders such that they are small, and the ranges on the x- and y-speed sliders are 0 to 10, with the default set at 1. These attributes are all set in the Info panel. Now we need to create a controller class. In the nib window, select the Classes tab, and create a subclass of NSObject called AnimationController. You can add outlets and actions to this class by clicking the class name in the list and opening the Info panel. We will have two action methods: one for each slider, and an outlet that gets connected to the AnimationView container. The actions are changeXSpeed: and changeYSpeed:. The outlet is simply called animationView. Now all we need to do is instantiate AnimationController, make the appropriate connections, and create the project files for this class.

Meanwhile, Back at the Ranch -- Speed Control

We now have a controller class that will communicate information between controls with which the user interacts and the animation view. Fundamentally, what we want to happen when the user slides the sliders is for the ball to change speed in the appropriate direction. To do this, we change the stepsize of the translation, dx or dy. This will have the effect of moving the ball a different distance in the same time interval.

Currently, however, no method exists for an outside object to change the AnimationView’s values of dx and dy. To enable this, we must add to AnimationView a couple of accessory methods called -setDx: and -setDy:, which will be invoked by AnimationController in its respective action methods. To AnimationView.h add the following method declarations:


- (void)setDx:(float)_dx;
- (void)setDy:(float)_dy;

The implementations of both of these methods is identical, save the xs and ys, so we’ll look only at the first. The method is simple, but has one small catch: the sliders have only positive values, but dx could be positive or negative.

What we would like to happen is for the slider to change the magnitude of dx and not the sign. We don’t want to set dx to a positive value if it's negative to begin with, and vice versa. This would result in unnatural and erratic motion.

To get around this, we check the sign of the current value of dx in setDx: and dy in setDy:, and if the sign is negative, we set dx to the negative of the argument value of setDx: and vice versa. One last thing we must do in these two methods is make the affine transform at reflect these changes. This is done in the same three-line way as we did in the -checkCollision: we release at, create a new instance of NSAffineTransform, and then set it to the desired translation. This is what it looks like:


- (void)setDx:(float)_dx
{
    if ( dx < 0 )
        dx = -_dx;
    else 
        dx = _dx;

    [at release];
    at = [[NSAffineTransform transform] retain];
    [at translateXBy:dx yBy:dy];
}

and for setDy:

- (void)setDy:(float)_dy
{
    if ( dy < 0 )
        dy = -_dy;
    else
        dy = _dy;

    [at release];
    at = [[NSAffineTransform transform] retain];
    [at translateXBy:dx yBy:dy];
}

And that's all there is to that. Now, going back to AnimationController, we set up the action methods to invoke these accessory methods. Again, the x and y are analogous. In the method changeXSpeed: we retrieve the new value of the slider, and pass it along to AnimationView via setDx:.


- (IBAction)changeXSpeed:(id)sender
{
    [animationView setDx:[sender floatValue]];
}

and similarly for changeYSpeed:

- (IBAction)changeYSpeed:(id)sender
{
    [animationView setDy:[sender floatValue]];
}

The receiver animationView is, of course, the outlet to the view container AnimationView. Simple as that. Now compile the code, play around with the controllers, and see what happens. While it's all good and well to change the speed of the ball, the fact that the ball stops moving when we use the control is slightly annoying. The solution to this problem is, of course, the next topic of discussion.

Multithreading

This annoying behavior is a side effect of our application having only one thread of execution. That is, only one thing can happen at a time, and the consequence is that when the control is being held and moved, the animation timer stops temporarily until we release the control. To fix this, we make our application multithreaded. The way this works is that when AnimationView is initialized, we detach a new thread from the main thread of execution. In the new thread, we handle the animation loop, and in the main thread we handle any user interaction. By doing this, we can create a much nicer animation and user interaction experience.

A new thread is created using the class method of NSThread +detachNewThreadSelector:toTarget:withObject:. This method takes as its arguments a selector to be invoked -— the method we want to execute in the new thread -- the target of the method indicated by the selector, and some object that contains arbitrary information we might want to access in the new thread. Note that threads are set up to execute a single method.

We’ll detach a new thread in the initialization code of initWithFrame:. The line creating a new thread will replace the code where we previously created our timer.

[NSThread detachNewThreadSelector:@selector(animate:) toTarget:self withObject:nil];

The thread we are creating here will execute a method called animate:, of the class AnimationView. The withObject: argument is nil since there is nothing we want to communicate with the new thread. The method -animate: is new to us; it will contain the code necessary to kick off and time the animation.

When we make a new thread, the first thing that must happen is the creation of an autorelease pool, and the last thing that the thread does before exiting is to release this pool. Until now, we’ve never had a need to create our own autorelease pool. Cocoa has always supplied one for us. Note that if you are using a thread to execute code that doesn’t use the Cocoa frameworks, then you don’t need to create and autorelease pool.

For example, if you had a method that ran some lengthy calculation using only the standard C math functions, then you wouldn’t need an autorelease pool. With this new context for using autorelease pools, you might want to review that documentation. With this, let's set up the framework for the method animate::


- (void)animate:(id)anObject
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    // execute our code here

    [pool release];
}

The argument to animate: is type id; this is the way a method must be set up for use by a thread. The object we specify in withObject: when we create a new thread is passed as an argument to animate:, and we can access it thusly. For more information about NSThread, take a gander through the class reference; it’s pretty short and straight to the point, with many useful insights about threads.

Our animation code is sandwiched between the two lines dealing with the autorelease pool. Lets take a look at that now.

Multithreaded Animation

When we use a separate thread for the animation timing, we’re going to do things a bit differently than above. Rather than use a timer that fires whenever we want a new frame, we will set up a continuous while statement that steps the animation. Within the while loop we will do two things: we will call stepAnimation, and we will tell the display to update itself. –animate: looks like this:


- (void)animate:(id)anObject
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    while (YES) {
        [self stepAnimation:nil];
        [self setNeedsDisplay:YES];
    }

    [pool release];
}

Note that -stepAnimation: also calls setNeedsDisplay:. We want to delete that line from stepAnimation:, since it is now called in the while loop of animate:. The reason we moved it is purely a matter of that message “fitting” in better with the function of –animate: as opposed to –stepAnimation:.

If you run this code as is, you will notice another disturbing behavior: the ball is just jumping all over the place —- nothing nice about that. The reason for this is that when we call [self setNeedsDisplay:YES] the display is not immediately updated. The main thread of execution deals with drawing via drawRect: when it gets some processor time; all we do is let the view know we want an update.

However, while the main thread wants to draw, the animation thread is looping through that while statement as fast as it can, and the main thread tries to update as soon as it can, but it simply can’t keep up. As a result, the animation progresses many more steps between each redraw. To remedy this problem, we tell the animation thread to take a break for a short while and let the other thread take care of business. This is accomplished using the NSThread method sleepUntilDate: after we call for an update to the display:


- (void)animate:(id)anObject
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    while (YES) {
        [self stepAnimation:nil];
        [self setNeedsDisplay:YES];
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.04]];
    }

    [pool release];
    [NSThread exit];
}

Also in Programming with Cocoa

Understanding the NSTableView Class

Inside StYNCies, Part 2

Inside StYNCies

Build an eDoc Reader for Your iPod, Part 3

Notice we choose the time interval to be the same as the NSTimer interval we had earlier. With this, you can run the animation and everything will look good. Notice that you can now move the sliders and the speed of the ball will change in real time -- no annoying pause. And that is one of the uses of multithreading: sending some code off to do its thing without locking the user out of the application.

As you’ll read in the NSThread reference, be careful about your multithreading, because when you get down to it, the processor really can only do one thing at a time. The real benefit of multithreading comes when you have multiple processors. Then you can really execute two or more things simultaneously.

The End

We have seen how we can do some pretty good multithreaded animation that lets the user interact with what's going on. The link to download the first version of this application was given above. The project folder for the second version can be downloaded here and finally, here is a link to a third project that works the animation in a slightly different way, allowing the user to scale the size of the ball. I leave you with that to ponder.

Michael Beam is a software engineer in the energy industry specializing in seismic application development on Linux with C++ and Qt. He lives in Houston, Texas with his wife and son.


Return to the Mac DevCenter.

Copyright © 2009 O'Reilly Media, Inc.