Which view was touched?

Drag the circles around the screen and place them on top of each other. Only the circle objects, not the big white object in the background, are touch-sensitive. And not all of each circle object is touch-sensitve. Only the region within the circumference is.

The class Circle in this app is a subclass of class UIView and is therefore rectangular. A Circle is in fact a perfect square, but you don’t see the square. All you see is the circle drawn on it, with a radius equal to half the length of a side of the square.

Until now, a touch-sensitive UIView has been touch-sensitive over its entire area. But a Circle is touch-sensitive only on or within the circumference of its circle. The four corners of the Circle outside the circumference are touch-insensitive, and are clear in color. See the pointInside(_:withEvent:) method of class UIView, and Hit Testing. The touched Circle brings itself to the front, i.e., it is placed on top of the other circles. See the bringSubviewToFront: method of class UIView.

A touch object passed to touchesMoved(_:withEvent:) has a previous location as well as a current location. The hypot function returns the distance from a given point to the origin using the Pythagrean theorem, and the origin of a Circle object is at the center of its circle.

When the init(decoder:) method of the app’s View object (or biggest UIView object) is called automatically by the app’s Main.storyboard file, the View is given a dummy size of 600 × 600 pairs of pixels. That’s why the init(decoder:) of this View does not attempt to give a size and position to the subviews.

At some later time, the size of this View is changed to the correct value (the size of the screen). When that happens, the View’s layoutSubviews method is called.

Source code in Hit.zip

  1. Class AppDelegate
  2. Class ViewController.
  3. Class View: the white background.
  4. Class Circle: a movable circle. Calls pointInside(_:withEvent:) and bringSubviewToFront(_:).

Things to try

  1. Change the three Circle properties of class View into a single property that is an array of three Circles. Remove the three existing Circle properties and replace them by the following.
    	let colors: [UIColor] = [
    		UIColor.greenColor(),
    		UIColor.cyanColor(),
    		UIColor.blueColor()
    	];
    		
    	let circles: [Circle] = [Circle]();	//Create an empty array of Circles.
    
    In the init(decoder:) method of the View, create the circles and addSubview them with a for-in loop.
    		for color in colors {
    			let circle: Circle = Circle(color: color);
    			circles.append(circle);
    			addSubview(circle);
    		}
    
    In the layoutSubviews method of the View, read the sizes and positions of the Circles from an array of tuples.
    		let tuples: [(center: CGPoint, radius: CGFloat)] = [	
    			(CGPointMake(x, y + size.height / 4),     size.width / 10),
    			(CGPointMake(x, y + size.height / 2),     size.width / 8),
    			(CGPointMake(x, y + size.height * 5 / 8), size.width / 5)
    		];
    
    		for var i = 0; i < circles.count; ++i {
    			circles[i].center = tuples[i].center;
    			circles[i].radius = tuples[i].radius;
    		}
    

  2. Class Circle contains the code to detect a touch and update the center property of each Circle. All of this could be done in class View instead. See the hitTest(_:withEvent:) method of class UIView in Puzzle.