Rectangles and Points
All drawing in Quartz involves rectangles. In Cocoa, you use the NSRect struct to describe a rectangle's location and dimensions:
(Rects don't automatically draw themselves. These are just diagrams to go with the examples.)
typedef struct {
NSPoint origin;
NSSize size;
} NSRect;
// make a rect at (0,0) which is 20x20
NSRect myRect;
myRect.origin.x = 0;
myRect.origin.y = 0;
myRect.size.width = 20;
myRect.size.height = 20;
The origin field is the "anchor point" of the rect, where the drawing starts. A point is described by NSPoint, which has x and y coordinates:
typedef struct {
float x;
float y;
} NSPoint;
// make three points on the canvas
NSPoint point1;
point1.x = 4;
point1.y = 11;
NSPoint point2;
point2.x = 12;
point2.y = 21;
NSPoint point3;
point3.x = 19;
point3.y = 8;
The size field of a rect is an NSSize, which holds a width and a height. There's no way to depict an instance of NSSize, it has to be part of a rect to be useful.
typedef struct {
float width;
float height;
} NSSize;
Much of 2D drawing in Cocoa is based on these three structs. Remember these are not Objective-C classes. You can't call methods on them directly, but there are functions that go with them.
All measurements in Quartz are float values, which gives you finer control of drawing than integer-based coordinates.
Convenience Functions
Cocoa has a number of functions for creating geometry structs. Most of them are listed in Foundation's NSGeometry.h file.
// make a point at coordinate 20,20
NSPoint newPoint = NSMakePoint ( 20, 20 );
// make a size of 100 wide x 100 high
NSSize newSize = NSMakeSize ( 100, 100 );
// use the previous point and size to make a rect
NSRect newRect = NSMakeRect ( newPoint.x,
newPoint.y,
newSize.width,
newSize.height );
// also can just do this
NSRect newRect = NSMakeRect ( 20, 20, 100, 100 );
Using these functions instead of creating the structs manually makes the code a bit more obvious and makes searching easier.
Coordinates in Quartz
The drawing area of a view in Cocoa is treated as a rect. Quartz calls this drawing area the "bounds." An NSPoint can represent any location in the view bounds.
The standard Quartz coordinate system is based on PDF model, which means drawing in a view starts in the bottom-left. This is what you see in geometry textbooks.
Sometimes it's easier to write drawing code if the origin is in the top-left. This is how things work in web page design, for example. Quartz calls this a flipped coordinate system.
You can easily convert points between standard and flipped views using NSView's convertPoint:fromView: and convertPoint:toView: methods.
Rects as Objects
Because they're not objects, you can't store the geometry structs in an NSArray, NSDictionary, or NSSet directly, but you can wrap them in an NSValue object:
NSRect newRect = NSMakeRect ( 20, 20, 100, 100 );
NSValue * rectObject = [NSValue valueWithRect: newRect];
NSMutableArray * myArray = [NSMutableArray array];
[myArray addObject: rectObject];
NSRect originalRect = [[myArray objectAtIndex: 0] rectValue];
NSValue has similar methods for NSPoint and NSSize. You can also log information about rects using the NSStringFromRect function:
NSRect newRect = NSMakeRect ( 20, 20, 100, 100 );
NSLog (@"%@", NSStringFromRect( newRect ));
Another function, NSRectFromString takes a properly-formatted rect description and returns an NSRect. Both sets of functions also exist for NSPoint and NSSize.
Derived Rects
Cocoa provides functions to create new rects based on existing ones. Here's how to make a rect which has the same dimensions as the original, but is shifted down and to the right (offset).
// create a rect, then get the 5x5 offset
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSOffsetRect ( rect1, 5, 5 );
You can use negative values for the offset if you want to move in the opposite directions.
Here's how to get the intersection area of two rects:
// get the common area between two rects
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSOffsetRect ( rect1, 5, 5 );
NSRect rect3;
rect3 = NSIntersectionRect ( rect1, rect2 );
Here's how to create a rect which encloses two other rects (a union).
// get a combination of two rects
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSOffsetRect ( rect1, 5, 5 );
NSRect rect3;
rect3 = NSUnionRect ( rect1, rect2 );
An inset rect is helpful if you want to create a outer boundry, then create a rect for the content inside:
// get a contracted version of a rect
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSInsetRect ( rect1, 5, 5 );
Comparing Rects and Points
Foundation provides a group of functions to check the equality of points and rects, as well as functions to see if points and rects are inside in other rects.
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSPoint point1 = NSMakePoint ( 8,21 );
BOOL isInRect;
isInRect = NSPointInRect ( point1, rect1 );
Below is a table of the most useful comparison functions. All of these functions return a YES or NO value.
Comparison Functions
NSEqualRects Are rects identical?
NSEqualPoints Are points identical?
NSEqualSizes Are sizes identical?
NSContainsRect Does the first rect contain the other?
NSIntersectsRect Do the rects at least partially overlap?
NSPointInRect Is the point inside the rect?
NSMouseInRect Is the mouse cursor in this rect?
NSIsEmptyRect Is the rect empty (no area)?
These functions are listed in Foundation's NSGeometry.h file.
Drawing
NSRects and NSPoints only describe geometry, they don't actually do drawing. Let's look at some primitive drawing functions in Cocoa's NSGraphics.h file.
NSColor * gray = [NSColor grayColor];
NSColor * white = [NSColor whiteColor];
// fill background
[gray set];
NSRectFill ( [self bounds] );
// fill target rect
NSRect rect1 = NSMakeRect ( 21,21,210,210 );
[white set];
NSRectFill ( rect1 );
The example above uses NSColor. When you call the -set method on a color object, Quartz uses it for all drawing until you set a new one.
Here's how to draw a border around a rect:
NSColor * gray = [NSColor grayColor];
NSColor * white = [NSColor whiteColor];
// fill background
[gray set];
NSRectFill ( [self bounds] );
// draw a border around target rect
NSRect rect1 = NSMakeRect ( 21,21,210,210 );
[white set];
NSFrameRectWithWidth ( rect1, 1 );
You can also call NSFrameRect if you just want to use the default line width.
Drawing Groups
It's often faster to draw an array of rects all at once instead of calling NSRectFill for each one individually.
Below is a more involved example which builds C-style arrays of NSRects and NSColors, then passes both to NSRectFillListWithColors.
// setup basics
[[NSColor grayColor] set];
NSRectFill ( [self bounds] );
int count = 12;
NSRect startingRect = NSMakeRect ( 21,21,50,50 );
// create arrays of rects and colors
NSRect rectArray [count];
NSColor * colorArray[count];
rectArray [0] = startingRect;
colorArray[0] = [NSColor redColor];
// populate arrays
int i;
NSRect oneRect = rectArray[0];
for ( i = 1; i < count; i++ )
{
// move 100 pixels to the right
oneRect.origin.x += 100;
// if the right edge doesn't fit, move down 100 pixels
if ( NSMaxX (oneRect) > NSMaxX ([self bounds]) )
{
oneRect.origin.x = startingRect.origin.x;
oneRect.origin.y += 100;
}
rectArray [i] = oneRect;
// increment color
colorArray[i] = [NSColor colorWithCalibratedHue: (i*0.04)
saturation: 1
brightness: 0.9
alpha: 1];
}
// use rect and color arrays to fill
NSRectFillListWithColors ( rectArray, colorArray, count );
// draw a 2 pixel border around each rect
[[NSColor whiteColor] set];
for ( i = 0; i < count; i++) {
NSFrameRectWithWidth ( rectArray[i], 2 );
}
it given the information about how to develop the iphone application and games.......
Showing posts with label animation objectivec. Show all posts
Showing posts with label animation objectivec. Show all posts
Tuesday, February 8, 2011
Monday, November 8, 2010
Creating Basic Animations on iPhone
Creating Basic Animations on iPhone |
Posted in:
One of the cool and easy features in Cocoa is animation. Today I am going to show you how to do a couple basic animations using Cocoa Touch for the iPhone. The animation that we are going to create will do two things: move and resize an object on the screen depending on where you touch it.
Before we get started if you haven't created any code for the iPhone yet I suggest you check out our Absolute Beginner's Guide. I also need to give props to the Stanford iPhone Development class, Alan Cannistraro did a demo with basic move animation.
Below is a short video of the application we are going to build. As mentioned earlier there are two things you can do. If you do a single click it will move the inner view to the click location. A double touch will resize the view and move it. The application will create a box from the two clicks and that will create the frame for the view.
Well, let's dig in. The first thing we need to do is create our project. I created a simple Window-Based Application and then we are going to add a view to our application. To do this we open our MainWindow.xib in Interface Builder, you can do this by double clicking on it. Once we have Interface Builder open we are going to open the Library window (Tools > Library). One of the items in there will be the View component, drag it into the main Window. Use the image below for reference.
The main view needs to be told that we are going to allow multiple simultaneous touches, this is set in the Attributes Inspector (Tools > Attributes Inspector). At the bottom there is Multiple Touch, make sure it is selected.
We also need to drag a second view to inside the first view. This second view is the one that is going to move and resize. The second view needs to have a few properties set on it, mainly it's size, color, and starting position. You need to bring up the Size Inspector (Tools > Size Inspector) then you can set the size and position - I went with position 0, 0 and size of 30, 30. Also in the Attributes Inspector uncheck User Interaction Enabled.
The next thing to do is create a custom UIView subclass. I named mine TouchView, this is where we are going to put all our code for this tutorial.
In
TouchView.h we need to add a single instance variable for our small inner view that we are going to move and resize. The variable has to be tagged as an IBOutlet so we can connect it to our view in Interface Builder. Our header file ends up looking like the following.#import <UIKit/UIKit.h>
@interface TouchView : UIView {
IBOutlet UIView *strechView;
}
@end
@interface TouchView : UIView {
IBOutlet UIView *strechView;
}
@end
In order for the view in Interface Builder to be associated with our custom class we need to set the class name of the view. This is done in Interface Builder by selecting our main view and opening Identity Inspector. At the very top is a the Class input, just enter
TouchView.Now we need to go back to Interface Builder and attach our little view to our instance variable. This is done by holding the
Control button and clicking on the main view and dragging to our small view. A popup will show the possible options to connect to, one should be our strechView.The rest of the code goes into
TouchView.m, the first step is to remove all the current code as we don't need any of the current implemented methods. We can add the following though - it handles when someone touches down on the screen.- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
}
{
}
The first thing we need to do is start the animation, this is done with the UIView class method
beginAnimations:context:. We pass it a name for the animation and nil for the context. I passed it@"MoveAndStrech" for the name. To finish and begin the animation we need to call the class methodcommitAnimations. What ever is done in between the two calls is what will happen in the animation. There are two quick methods to call to set the animation behavior, setAnimationDuration: andsetAnimationBeginsFromCurrentState. The first is is pretty self explanatory the second tells the animation that it should start from the present state of the view. Our updated touchesBegan follows.- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[UIView beginAnimations:@"MoveAndStrech" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView commitAnimations];
}
{
[UIView beginAnimations:@"MoveAndStrech" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView commitAnimations];
}
The rest of the code is going to actually handle the updating of the view. The first case we are going to handle is if the touch count is one. If so, we grab the UITouch object from the
touches set. We then set the center of ourstrechView to the location of the touch relative to our main view (self). So in two simple lines we now have a moving view that moves based on the location of touch.- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[UIView beginAnimations:@"MoveAndStrech" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationBeginsFromCurrentState:YES];
if([touches count] == 1) {
UITouch *touch = [touches anyObject];
strechView.center = [touch locationInView:self];
}
[UIView commitAnimations];
}
{
[UIView beginAnimations:@"MoveAndStrech" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationBeginsFromCurrentState:YES];
if([touches count] == 1) {
UITouch *touch = [touches anyObject];
strechView.center = [touch locationInView:self];
}
[UIView commitAnimations];
}
Finally, we need to handle the resizing of the view. I'll run through the code right after the following.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[UIView beginAnimations:@"MoveAndStrech" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationBeginsFromCurrentState:YES];
if([touches count] == 1) {
UITouch *touch = [touches anyObject];
strechView.center = [touch locationInView:self];
} else if([touches count] == 2) {
NSArray *touchArray = [touches allObjects];
UITouch *touchOne = [touchArray objectAtIndex:0];
UITouch *touchTwo = [touchArray objectAtIndex:1];
CGPoint pt1 = [touchOne locationInView:self];
CGPoint pt2 = [touchTwo locationInView:self];
CGFloat x, y, width, height;
if(pt1.x < pt2.x) {
x = pt1.x;
width = pt2.x - pt1.x;
} else {
x = pt2.x;
width = pt1.x - pt2.x;
}
if(pt1.y < pt2.y) {
y = pt1.y;
height = pt2.y - pt1.y;
} else {
y = pt2.y;
height = pt1.y - pt2.y;
}
strechView.frame = CGRectMake(x, y, width, height);
}
[UIView commitAnimations];
}
{
[UIView beginAnimations:@"MoveAndStrech" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationBeginsFromCurrentState:YES];
if([touches count] == 1) {
UITouch *touch = [touches anyObject];
strechView.center = [touch locationInView:self];
} else if([touches count] == 2) {
NSArray *touchArray = [touches allObjects];
UITouch *touchOne = [touchArray objectAtIndex:0];
UITouch *touchTwo = [touchArray objectAtIndex:1];
CGPoint pt1 = [touchOne locationInView:self];
CGPoint pt2 = [touchTwo locationInView:self];
CGFloat x, y, width, height;
if(pt1.x < pt2.x) {
x = pt1.x;
width = pt2.x - pt1.x;
} else {
x = pt2.x;
width = pt1.x - pt2.x;
}
if(pt1.y < pt2.y) {
y = pt1.y;
height = pt2.y - pt1.y;
} else {
y = pt2.y;
height = pt1.y - pt2.y;
}
strechView.frame = CGRectMake(x, y, width, height);
}
[UIView commitAnimations];
}
In order to move and resize the view we need to grab the two touches. This is easily done using the
allObjectsinstance method on the touches set. We then get both locations and determine the upper x and y coordinates. The width and height can be calculated by getting the difference of the points. the final thing to do is set the frame of the view using CGRectMake. The entire TouchView.m is below.#import "TouchView.h"
@implementation TouchView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[UIView beginAnimations:@"MoveAndStrech" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationBeginsFromCurrentState:YES];
if([touches count] == 1) {
UITouch *touch = [touches anyObject];
strechView.center = [touch locationInView:self];
} else if([touches count] == 2) {
NSArray *touchArray = [touches allObjects];
UITouch *touchOne = [touchArray objectAtIndex:0];
UITouch *touchTwo = [touchArray objectAtIndex:1];
CGPoint pt1 = [touchOne locationInView:self];
CGPoint pt2 = [touchTwo locationInView:self];
CGFloat x, y, width, height;
if(pt1.x < pt2.x) {
x = pt1.x;
width = pt2.x - pt1.x;
} else {
x = pt2.x;
width = pt1.x - pt2.x;
}
if(pt1.y < pt2.y) {
y = pt1.y;
height = pt2.y - pt1.y;
} else {
y = pt2.y;
height = pt1.y - pt2.y;
}
strechView.frame = CGRectMake(x, y, width, height);
}
[UIView commitAnimations];
}
@end
@implementation TouchView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[UIView beginAnimations:@"MoveAndStrech" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationBeginsFromCurrentState:YES];
if([touches count] == 1) {
UITouch *touch = [touches anyObject];
strechView.center = [touch locationInView:self];
} else if([touches count] == 2) {
NSArray *touchArray = [touches allObjects];
UITouch *touchOne = [touchArray objectAtIndex:0];
UITouch *touchTwo = [touchArray objectAtIndex:1];
CGPoint pt1 = [touchOne locationInView:self];
CGPoint pt2 = [touchTwo locationInView:self];
CGFloat x, y, width, height;
if(pt1.x < pt2.x) {
x = pt1.x;
width = pt2.x - pt1.x;
} else {
x = pt2.x;
width = pt1.x - pt2.x;
}
if(pt1.y < pt2.y) {
y = pt1.y;
height = pt2.y - pt1.y;
} else {
y = pt2.y;
height = pt1.y - pt2.y;
}
strechView.frame = CGRectMake(x, y, width, height);
}
[UIView commitAnimations];
}
@end
That pretty much wraps it up for this tutorial. If you have any questions feel free to leave a comment or head on over to the forums.
Subscribe to:
Posts (Atom)