Channels ▼
RSS

Mobile

The iPhone Isn't Easy


Views Within Views

Unlike the static scene in SpaceView where I used a nib file, I decided that the view displaying the spaceship should be generated by code. The reason is that it is a dynamic object, moving about in response to data from the physics engine. The view's image would also change in appearance, depending on whether the spaceship engine was firing. I created a simple view, ShipView, to manage the rocket's appearance (Listing Three). Two images, rocket_firing,png and rocket.png, would show the ship with its engine either firing or not. The method upDateShip changes the view's appearance by loading one or the other of these images. Besides drawing itself, ShipView doesn't do anything else: SpaceViewController is responsible for rotating the ShipView, and plotting its position. Once these updates are done, SpaceViewController directs ShipView to redraw itself, so that appears in the new position, orientation, and with the proper image.

Listing Three

#import "ShipView.h"

@implementation ShipView

@synthesize shipImage;
@synthesize shipThrustingImage;
@synthesize shipNotThrustingImage;

- (id)init {
	// Retrieve the images for the view and determine the size of one
	shipNotThrustingImage = [UIImage imageNamed:@"rocket.png"];
	shipThrustingImage = [UIImage imageNamed:@"rocket_firing.png"];
	
	CGRect shipFrame = CGRectMake(0, 0, shipNotThrustingImage.size.width, shipNotThrustingImage.size.height);	
	// Set self's frame to encompass the image
	if (self = [self initWithFrame:shipFrame]) {		
		self.opaque = NO;
		shipImage = shipNotThrustingImage;
	}
	return self;
}

- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        // Initialization code
    }
    return self;
}

- (void)upDateShip:(int)imageSelection {

	if (imageSelection == kNoThrust) {
		shipImage = shipNotThrustingImage;
	}
	if (imageSelection == kThrust) {
		shipImage = shipThrustingImage;
	}
}

- (void)drawRect:(CGRect)rect {

	// Drawing code
	[shipImage drawAtPoint:(CGPointMake(0.0, 0.0))];
}

- (void)dealloc {
	[shipImage release];
	[shipThrustingImage release];
	[shipNotThrustingImage release];
    [super dealloc];
}

@end

Creating the virtual control pad was a bit tougher. One of the best features about the iPhone is that its touch screen allows you to design the UI to suit the needs of your app. One of the worst features of the iPhone is that you have to design the UI. You don't want come up with an unwieldy interface that could frustrate a user.

However, the first order of business was to figure out to respond to the touch events received by the control pad's view. The intuitive interface could come later. Although the control pad's position was fixed, I designed the ControlPadView class, like ShipView's, to be generated programmatically. This allowed its appearance to change in response to a touch, and to potentially support a user's preference to change its position. ControlPadView's code was much simpler than ShipView's, as it only had to load one image. For a control pad image, I started with three buttons: a left one to turn the ship starboard, one in the middle to fire the rocket, and the third to command a turn to port.

A view responds only to touch events that fall within its frame, and these events correspond to several types of messages. There's touchesBegan, which is sent when you place your finger on the screen, and touchesEnded, which is sent when you lift your finger from it. A touchesMoved message is sent when the iPhone OS detects finger motion across the screen. If you touch the screen and release it quickly, the iPhone OS interprets the two closely spaced events as a tap and advances a counter. This counter can be read to determine how many times the screen was tapped.

With this set of events, I could implement the control pad and other actions, as in Listing Four. For a touchesBegan event, I first determined if it was on the ControlPadView or a view with an information button. If the touch occurred in ControlPadView, I next obtained the touch's coordinates within the view to determine what button had been touched. I had designed the buttons to fill the entire view vertically, so all the code had to do was compare the horizontal displacement with specific constants to determine which button was "pushed". For the two steering buttons, a shipTurning variable is set to signal a turn and its direction. A touch on the thrust button sets a variable named thrust. When ShipView redraws itself, it would display the image of the ship firing its engine. A touch on the information button loads the help view. This code is not shown here, but is available online.

Listing Four

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

	UITouch *touch = [touches anyObject];
	if ([touch view] == controlPad) {

		location = [touch locationInView:controlPad];

		int disp;
		disp = location.x;
		if (disp <= kPortPad) {
			shipTurning = kPortTurn;
		} else if (disp > kPortPad && disp <= kFireEnginePad) {
			thrust = kThrust;
		} else if (disp >= kStarboardPad) {
			shipTurning = kStarBoardTurn;
		}
		controlPad.alpha = 0.75;
		return;
	}
	if ([touch tapCount] == 2) {
		[self doReset];
		return;
	}
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

	thrust = kNoThrust;
	shipTurning = 0.0;
	
	[shipView upDateShip:kNoThrust];
	controlPad.alpha = 0.5;
	infoIcon.alpha = 0.5;
}

To provide visual feedback when the control pad was touched, I cranked up ControlPadView's alpha channel property up so that the buttons would be less translucent and appear to light up. Finally, just in case things went horribly wrong with the program, I had touchesBegan watch the tap counter for two taps anywhere on the screen, which would reset the physics engine and the app's operating mode.

When the user lifts their figure, the touchesEnded method cleans up by clearing the shipTurning and thrust variables, and having ShipView update its image. I also reset the alpha property of the ControlPadView back its default value, which made the buttons partially translucent again. The ability to manipulate the properties of views, particularly their alpha channel and visibility, allows you to craft a slick interface with little effort and code.

Now it was time to put things together. The UIApplicationDelegate loads, and it in turn loads the main window nib and SpaceViewController. SpaceViewController in turn loads SpaceView 's nib and receives a message, viewDidLoad. This message signals SpaceViewController that SpaceView loaded without trouble. I used the viewDidLoad method to add the views of the spaceship, the control pad, and info button into SpaceView. The order in which the views are drawn is important, because you can layer views over one another. By drawing the ship first, it would be under the control pad and info button, but this wasn't a problem because their alpha property made them translucent. The UIView class also provides methods for changing the order of views on-the-fly if your design requires it. The last thing viewDidLoad does is make an instance of NSTimer. It implements a periodic timer that calls the game loop method every 0.075 seconds.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video