macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Animating Graphics in Cocoa, Part 1
Pages: 1, 2, 3, 4, 5

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.

Pages: 1, 2, 3, 4, 5

Next Pagearrow