How to Track Touches in Your iOS App - dummies

How to Track Touches in Your iOS App

By Jesse Feiler

It would be nice to be able to drag a car and place it anywhere on the screen in your iOS app. Here, you will find out how to code for dragging an object, as well as how touches work on an iOS device.

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 (contained) 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 TestDriveController’s superclass, UIResponder, from which the view controller is derived.

As the user continues to touch 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. The touchesCancelled:withEvent: message, on the other hand, 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) to the TestDriveController.m implementation file.

@interface TestDriveController () {
 AVAudioPlayer *backgroundAudioPlayer;
 SystemSoundID burnRubberSoundID;
 BOOL touchInCar;
}
–d

Next, add the touchesBegan: method to TestDriveController.m to start tracking touches. (You’re actually overriding this method because UIViewController inherited it from the UIResponder base class.)

- (void)touchesBegan:(NSSet *)touches withEvent:
          (UIEvent *)event
{
 UITouch *touch = [touches anyObject];
 if (CGRectContainsPoint(self.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 NSSet, use the anyObject method — it returns one of the objects in the set. For our purposes here, you’re assuming just one object — but you might want to explore this issue further on your own so that you can understand how to handle additional possibilities.

The following code shows how to set up the anyObject method:

UITouch *touch = [touches anyObject];

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

 if (CGRectContainsPoint(self.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:

self.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’re 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 an itty-bitty gas pedal.

If it’s determined that the touch is in the car, you assign YES to the touchInCar instance variable; if it’s 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 finger over the screen.

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 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-13 to TestDriveController.m to override that method, which will enable you to move the car to where the user’s finger is.

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

If the first touch was in the Car view (touchInCar is YES), you assign car’s center property to the touch coordinate. 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’s interesting to observe that when you position the car next to a button, it will travel under that button when you touch the Test Drive button. This feature illustrates the subview structure.

Experiment with moving the car around and then using the Test Drive button. If there’s anything wrong with your formulas for positioning the car during the Test Drive, you’ll see it when the car starts from a different place.