Detect a touch

Source code in Touch.zip

  1. main.m
  2. Class TouchAppDelegate
  3. Class View: the big white view that fills the window
  4. Class LittleView: the little yellow view that moves around
  5. Default-568h@2x.png (640 × 1136 pixels)

The three views

The app has a main function in the file main.m, and a TouchAppDelegate in the files TouchAppDelegate.h and TouchAppDelegate.m. It also has three views:

  1. A big white View, created by the application:didFinishLaunchingWithOptions: method of the TouchAppDelegate. The View occupies the entire the window (except for the status bar). The View contains the following view.

  2. A yellow LittleView, created by the (initWithFrame: method of) the View. The LittleView is 80 × 40 pixels and moves around in the white View.

  3. A window, created by (the application:didFinishLaunchingWithOptions: method of) the TouchAppDelegate. The window contains the white View.

The app delegate object, the white View object, and the yellow LittleView object required custom code. I therefore had to derive a class TouchAppDelegate from class NSObject <UIApplicationDelegate>, and classes View and LittleView from class UIView. The other object, the window, required no custom code. I was able to make it an instantiation of the pre-written class UIWindow.

The View receives a set of touch objects.

The app delegate puts the View into the window. A View can respond to a touch because class View is derived from class UIView, which is derived from class UIResponder. A View receives the message touchesBegan:withEvent: when one or more touches begin. This method is inherited from UIResponder.

The first argument of touchesBegan:withEvent: is an NSSet of UITouch objects representing the touches that have just begun. To verify that it is safe to call anyObject, the if statement verifies that the set contains at least one element. (If it doesn’t contain at least one element, touchesBegan:withEvent: should not have been called.) The set should contain at most one element because we never set the multipleTouchEnabled property of the View to YES.

The second argument of touchesBegan:withEvent: is an UIEvent object containing all the touches currently in progress, not merely the ones that have just begun. Again, we expect that only one touch will be in progress, so we don’t bother to look at the second argument.

If a set contains exactly one element, the easiest way to get the element is by calling the method anyObject. Since this set is a set of UITouch objects, the return value of this call to anyObject will be a pointer to a UITouch object. The method locationInView: gets us the x, y coördinates of the touch object within the specified view.

The keyword self in initWithFrame: and touchesBegan:withEvent: refers to the View object.

The Mac screen is not sensitive to fingers; you’ll have to click on the iOS Simulator to simulate a touch. iOS gives us only the x, y coördinates of a touch. Android also gives us the area and pressure.

Things to try

  1. The big View’s initWithFrame: puts the LittleView in the upper left corner of the big View. Have it put the LittleView in the exact center of the View. Create f as follows.
    		CGFloat w = 80;   //width in pixels of littleView
    		CGFloat h = 40;   //height in pixels of littleView
    		CGRect b = self.bounds;
    
    		CGRect f = CGRectMake(
    			b.origin.x + (b.size.width - w) / 2,
    			b.origin.y + (b.size.height - h) / 2,
    			w,
    			h
    		);
    
    Or simplify the CGRectMake that creates f by putting the origin of the big View at the center of the big View.
    		//Put the origin at the center of the big view.
    		CGRect b = self.bounds;
    
    		self.bounds = CGRectMake(
    			-b.size.width / 2,
    			-b.size.height / 2,
    			b.size.width,
    			b.size.height
    		);
    
    		//Put the LittleView at the origin of the big view.
    		CGFloat w = 80;   //width in pixels of LittleView
    		CGFloat h = 40;   //height in pixels of LittleView
    
    		CGRect f = CGRectMake(
    			-w / 2,
    			-h / 2,
    			w,
    			h
    		);
    

  2. The LittleView jumps as soon as the touch begins because we wrote a method named touchesBegan:withEvent:. Rename it touchesEnded:withEvent: and verify that the LittleView now jumps when the touch ends. Then change the name of the method back to touchesBegan:withEvent:.

  3. Let’s make the LittleView draggable. Change the name of the method to touchesMoved:withEvent: and verify that you can drag on the LittleView. Then change the name of the method back to touchesBegan:withEvent:. (touchesMoved:withEvent: can call previousLocationInView: as well as locationInView:.)

    There is a parallel series of methods for detecting when the iPhone is moved: motionBegan:withEvent:, motionEnded:WithEvent:, etc. See Motion Events and Remote Control Events.


  4. Let’s keep the LittleView draggable. In addition, it will become green when we start to drag it, and change back to to yellow when the drag is ended. Remove the existing touchesBegan:withEvent:, and insert these three methods.
    - (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event {
    	littleView.backgroundColor = [UIColor greenColor];
    }
    
    - (void) touchesMoved: (NSSet *) touches withEvent: (UIEvent *) event {
    	littleView.center = [[touches anyObject] locationInView: self];
    }
    
    - (void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event {
    	littleView.backgroundColor = [UIColor yellowColor];
    }
    

  5. There is one more method the the conscientious programmer could add to class View: touchesCancelled:withEvent:. This method is called if a telephone call arrives, or when the Home button is pressed.