iPhone
iPhone SDK Example: Moving And Rotating Image Part 2
May 11, 2009
0

Continuing from Part 1, this part will demonstrate the followings:

* Loading image dynamically using UIImageView
* Using array

Step 1

Open the Ball project created in Part1.

Step 2

Import another image into the Resources Group (you can use the same image as previously, but it won’t be as much fun).

Step 3

We need to create another variable to store the second Ball, so open  BallViewController.h.  Let’s call the second ball mBall2, which isn’t a very good name and it a real application you’d be better of using some sort of array.

#import <UIKit/UIKit.h>

@class Ball;

@interface BallViewController : UIViewController {
 IBOutlet    Ball* mBall;
 Ball* mBall2;
}
@property(retain, nonatomic) IBOutlet Ball* mBall;
@property(retain, nonatomic) Ball* mBall2;
@end

Notice a few things: we are not including the IBOutlet modifier.  This is because we won’t need to reference mBall2 from them Interface Builder.  It does not hurt to include it however, if you do.

Step 4

Now we are ready to load the image.  Edit BallViewController.m and use initWithImage to load the image.  Like previously, the viewDidLoad function is a good place to do the initialization:

#import "BallViewController.h"
#import "Ball.h"

@implementation BallViewController
@synthesize mBall;
@synthesize mBall2;

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
 [super viewDidLoad];
 [mBall setSpeedX:5 Y:5];
 [mBall retain];

 mBall2=[[Ball alloc] initWithImage:[UIImage imageNamed:@"ball2.png"]];
 [self.view addSubview:mBall2];
 [mBall2 setSpeedX:10 Y:10];
 mBall2.center=self.view.center;
 [mBall2 retain];
 [NSTimer scheduledTimerWithTimeInterval:1.0/30.0
    target:self selector:@selector(moveBall) userInfo:nil repeats:YES];  
}

Since Ball is derived from UIImageView, we can use UIImageView::initWithImage to load the image. However, initWithImage takes an UIImage object as its parameter, so we first need to create an UIImage object to pass to initWithImage.

 mBall2=[[Ball alloc] initWithImage:[UIImage imageNamed:@"ball2.png"]];

We then added the Ball into the current view (which is the entire stage), again using addSubview.  We also positioned it on the center of the stage.

 [self.view addSubview:mBall2];
 mBall2.center=self.view.center;

I assigned 10 as the speed, but you can try other numbers if you like.  If you do Build and Run now, the ball should appear on the center of the screen.

Step 5

Now we need to move the ball, by calling the same move function that we declared in the Ball class.   So now you see that by encapsulating and extending the UIImageView, it is easier to add functionality without having to rewrite the code that moves the image.

-(void)moveBall
{
 [mBall move];
 [mBall2 move];   
}

While at it, let’s also release the ball.

 

- (void)dealloc {
 [mBall release];
 [mBall2 release];
 [super dealloc];
}

Download the project.

Loading image dynamically does requires more code than loading with Interface Builder, but it has many benefits including more control over the image and the ability to easily create many instances of the image. Let’s do that next.

Using Array To Store Objects

To store multiple objects, we will be using NSMutableArray.  Cocoa also has NSArray, which is similar but not change-able.  For our purpose, using NSArray would have sufficed, but in a real game, you’ll most likely be using NSMutableArray since game-states usually changes many times.

Open up BallViewController.h and add the NSMutableArray variable, and remove the existing Ball variables since we are keeping them in the array instead. It should look like this now:

#import <UIKit/UIKit.h>

@class Ball;

@interface BallViewController : UIViewController {
 NSMutableArray *mBallArray;
}
@property(retain, nonatomic) NSMutableArray* mBallArray;
@end

Open up the BallViewController.xib, and remove the ImageView of the ball (the one we added on Part 1 of the tutorial).  We won’t be using Interface Builder stuff anymore since we’re going to create all the balls dynamically.

Now let’s add the Ball objects into the array.  Open up BallViewController.m and follow the following code.

#import "BallViewController.h"
#import "Ball.h"

@implementation BallViewController
@synthesize mBallArray;

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
 [super viewDidLoad];       

 mBallArray = [[NSMutableArray alloc] init];
 int i;
 for (i = 0; i < 10; i++)
 {
   Ball* ball=[[Ball alloc] initWithImage:[UIImage imageNamed:@"ball.png"]];
   [mBallArray addObject:ball];
   [self.view addSubview:ball];
   int speed=random() % 10;
   [ball setSpeedX:speed Y:speed];
   ball.center=self.view.center;   
 }

 [NSTimer scheduledTimerWithTimeInterval:1.0/30.0
    target:self selector:@selector(moveBall) userInfo:nil repeats:YES];       
}

We are using a loop to create 10 balls, adding each to the array using addObject.  I also set the speed to be a random number, using random function.  Notice the addObject function calls that were made to add the ball into the array.

To move the balls, we just need to iterate the array and call move on each object. I am using objectAtIndex, but you can also use an enumerator

-(void)moveBall
{
 int i;
 int numberOfBalls=[mBallArray count];
 for (i = 0; i <numberOfBalls; i++)
 {
   Ball* ball=[mBallArray objectAtIndex:i];    
   [ball move];
 }
}

 

Let’s not forget to be nice and release the objects.

- (void)dealloc {
 int i;
 int numberOfBalls=[mBallArray count];
 for (i = 0; i <numberOfBalls; i++)
 {
   Ball* ball=[mBallArray objectAtIndex:i];   
   [ball release];
 }

 [mBallArray release];
 [super dealloc];
}

Build and Run.  The screen should show balls moving in random speed and bouncing when hitting the screen-edges.  Note: you may see fewer than 10 because some may have identical speed.  To remedy that, you can extends the range of the randomization.

balltutorial2a

PS: If you get an error like below, then you need to remove the ImageView from the Interface Builder. (Open up the BallViewController.xib, and remove the ImageView of the ball, as we’re no longer using it and we had deleted the mBall variable.

Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key mBall.’

Optimization

iPhone has a limited memory, so we should design our application to use memory efficiently.  Do you see anything that can be optimized?   See how the UIImage is being created for every Ball object?  We can just reuse one UIImage, like this:

- (void)viewDidLoad {
 [super viewDidLoad];       

 mBallArray = [[NSMutableArray alloc] init];
 int i;
 UIImage* image=[UIImage imageNamed:@"ball.png"];
 for (i = 0; i < 10; i++)
 {
   Ball* ball=[[Ball alloc] initWithImage:image];
   [mBallArray addObject:ball];
   [self.view addSubview:ball];
   int speed=random() % 10;
   [ball setSpeedX:speed Y:speed];
   ball.center=self.view.center;   
 }

 [NSTimer scheduledTimerWithTimeInterval:1.0/30.0
    target:self selector:@selector(moveBall) userInfo:nil repeats:YES];       
}

Using Multiple Images

Let’s load multiple UIImage and alternate between the two.  Below I loaded ball1.png and ball2.png.  Then in the loop, I alternated between the two images.

- (void)viewDidLoad {
 [super viewDidLoad];       

 mBallArray = [[NSMutableArray alloc] init];
 UIImage* image1=[UIImage imageNamed:@"ball1.png"];
 UIImage* image2=[UIImage imageNamed:@"ball2.png"];
 int i;   
 for (i = 0; i < 10; i++)
 {
   UIImage* image;
   if ((i%2)==0)
     image=image1;
   else
     image=image2;

   Ball* ball=[[Ball alloc] initWithImage:image];
   [mBallArray addObject:ball];
   [self.view addSubview:ball];
   int speed=random() % 10;
   [ball setSpeedX:speed Y:speed];
   ball.center=self.view.center;   
 }

 [NSTimer scheduledTimerWithTimeInterval:1.0/30.0
   target:self selector:@selector(moveBall) userInfo:nil repeats:YES];       
}

Download the project.