Home / iPhone Tips and Tutorials

Tracking Touches

While Skippy (the inspiration for the RoadTrip app) was pretending to drive across country, he said that it would be nice to be able to drag the car and place it anywhere on the screen. And because his wish is my command, in this section I explain how to code for dragging an object, as well as how touches work on the iPhone.

The touch of a finger (or lifting it from the screen) adds a touch event to the application's event queue, where it's encapsulated in a UIEvent object. A UITouch object exists for each finger touching the screen, which enables you to track individual touches.

The touchesBegan:withEvent: message is sent when one or more fingers touch down in a view. This message is a method of the RTViewController's superclass, UIResponder, from which the view controller is derived.

As the user manipulates the screen with his or her fingers, the system reports the changes for each finger in the corresponding UITouch object, thereby sending the touchesMoved:withEvent: message. The touchesEnded:withEvent: message is sent when one or more fingers lift from the associated view, or when the touchesCancelled:withEvent: message is sent when a system event (such as a low-memory warning) cancels a touch event.

In this app, you need be concerned only with the first two methods just described.

To begin the process of responding to a touch event, add a new instance variable (bolded in Listing-12) to the RTViewController.m implementation file.

Listing 10-12: Updating the RTViewController Implementation

@interface RTViewController () {

  AVAudioPlayer *backgroundAudioPlayer;
  SystemSoundID burnRubberSoundID;
  BOOL touchInCar;
}

Next, add the touchesBegan: method in Listing-13 to RTView Controller.m to start tracking touches. You are actually overriding this method because you have inherited it from the UIResponder base class.

Listing-13: Overriding touchesBegan:

- (void)touchesBegan:(NSSet *)touches withEvent:
				    (UIEvent *)event
{
  UITouch *touch = [touches anyObject];
  if (CGRectContainsPoint(car.frame,
			[touch locationInView:self.view]))
    touchInCar = YES;
  else {
    touchInCar = NO;
    [super touchesBegan:touches withEvent:event];
  }
}

As mentioned previously, the touchesBegan:withEvent: message is sent when one or more fingers touch down in a view. The touches themselves are passed to the method in an NSSet object - an unordered collection of distinct elements.

To access an object in the NSSet object, use the anyObject method - it returns one of the objects in the set. For the purpose here, you are assuming just one - but you might want to explore this issue further on your own so that you can learn to handle additional possibilities. The following code shows how that is done:

UITouch *touch = [touches anyObject];

Next, have the code determine whether the user's touch event is in the car (UIImage) view:

if (CGRectContainsPoint(car.frame,
		[touch locationInView:self.view]))

CGRectContainsPoint is a function that returns YES when a rectangle (view coordinates) contains a point. You specify the car's frame as the rectangle:

car.frame

and you specify the point by sending the locationInView: message to the touch.

locationInView:self.view

locationInView: returns the current location of the receiver in the coordinate system of the given view. In this case, you are using the main view, but you might want to change the view if you're trying to determine the location within another view for example, maybe the user is touching the itty-bitty gas pedal. (By the way, in this application, the car does not have a gas pedal.)

If the touch is in the car, you assign YES to the touchInCar instance variable; if it's not, not you assign NO and forward the message up the responder chain. You use touchInCar later to determine whether the user is dragging the car around, or just running his or finger over the phone.

The default implementation of touchesBegan: does nothing. However, subclasses derived directly from UIResponder, particularly UIView, forward the message up the responder chain. To forward the message to the next responder, send the message to super (the superclass implementation).

If you override touchesBegan:withEvent: without calling super (a common use pattern), you must also override the other methods for handling touch events, if only as stub (empty) implementations.

Multiple touches are disabled by default. To allow your app to receive multiple touch events, you must set the a multipleTouchEnabled property of the corresponding view instance to YES.

As users merrily move the car around the screen (perhaps saying zoom zoom to themselves), your app is constantly being sent the touchesMoved: message. Add the code in Listing 10-14 to RTViewController.m to override that method.

Listing-14: Overriding touchesMoved:

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

  if (touchInCar) {
    UITouch* touch = [touches anyObject];
    car.center = [touch locationInView:self.view];
  }
  else
    [super touchesMoved:touches withEvent:event];
}

If the first touch was in the car view (touchInCat is YES), you assign the car's center property to the touch coordinate. As I explain in "Animating a View" early in this article, when you assign a new value to the center property, the view's location is immediately changed. Otherwise, you ignore the touch and forward the message up the responder chain.

It is interesting to observe that when you position the car next to a button, it will travel under that button when you touch the TestDrive button. This feature illustrates the subview structure. Because I had you add the buttons last (they are subviews of the Main view), they are displayed on top of the subviews (car) that you added earlier.

[Previous] [Contents] [Next]