Article

Implementing a Multi-Touch App

Learn how to create a simple app that handles multitouch input.

Overview

Consider the app shown in Figure 1, where a single main view that draws a gray circle at each touch location. When a touch ends, the circle disappears. When the user’s fingers move, the underlying circles move with them.

Figure 1

Handling multiple touches in a view

A screenshot from an app that draws gray circles on the screen at multiple touch points simultaneously.

The creation of this app begins with the Single View app template in Xcode. This type of app has a single view controller whose view—in this case a custom subclass of UIView called TouchableView—fills the screen. The view contains only a label initially, but the app programmatically adds subviews later. Figure 2 shows the storyboard for the view controller.

Figure 2

The app's main view

A screenshot of a storyboard in Interface Builder showing a single view controller, whose view is of the custom type TouchableView.

Implement the TouchableView Class

The TouchableView class overrides the inherited touchesBegan(_:with:), touchesMoved(_:with:), touchesEnded(_:with:), and touchesCancelled(_:with:) methods. These methods handle the creation and management of subviews that draw the gray circles at each touch location. Specifically, these methods do the following:

Listing 1 shows the main implementation of the TouchableView class and its touch handling methods. Each method iterates through the touches and performs the needed actions. The touchViews dictionary uses the UITouch objects as keys to retrieve the subviews the user is manipulating onscreen.

Listing 1

Handling the touch events

class TouchableView: UIView {
   var touchViews = [UITouch:TouchSpotView]() 
 
   override init(frame: CGRect) {
      super.init(frame: frame)
      isMultipleTouchEnabled = true
   }
 
   required init?(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      isMultipleTouchEnabled = true
   }
 
   override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
      for touch in touches {
         createViewForTouch(touch: touch)
      }
   }
 
   override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
      for touch in touches {
         let view = viewForTouch(touch: touch) 
         // Move the view to the new location.
         let newLocation = touch.location(in: self)
         view?.center = newLocation
      }
   }
 
   override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
      for touch in touches {
         removeViewForTouch(touch: touch)
      }
   }
 
   override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
      for touch in touches {
         removeViewForTouch(touch: touch)
      }
   }
  
   // Other methods. . . 
}

Several helper methods handle creation, management, and disposal of the subviews, as shown in Listing 2. The createViewForTouch method creates a new TouchSpotView object and adds it to the TouchableView object, animating the view to its full size. The removeViewForTouch method removes the corresponding subview and updates the class data structures. The viewForTouch method is a convenience method for retrieving the view associated with a given touch event.

Listing 2

Managing the subviews

func createViewForTouch( touch : UITouch ) {
   let newView = TouchSpotView()
   newView.bounds = CGRect(x: 0, y: 0, width: 1, height: 1)
   newView.center = touch.location(in: self)
 
   // Add the view and animate it to a new size.
   addSubview(newView)
   UIView.animate(withDuration: 0.2) {
      newView.bounds.size = CGSize(width: 100, height: 100)
   }
 
   // Save the views internally
   touchViews[touch] = newView
}
 
func viewForTouch (touch : UITouch) -> TouchSpotView? {
   return touchViews[touch]
}
 
func removeViewForTouch (touch : UITouch ) {
   if let view = touchViews[touch] {
      view.removeFromSuperview()
      touchViews.removeValue(forKey: touch)
   }
}

Implement the TouchSpotView Class

The TouchSpotView class (shown in Listing 3) represents the custom subviews that draw the gray circles onscreen. TouchSpotView maintains its circular shape by setting the cornerRadius property of the layer each time its bounds property changes.

Listing 3

Implementation of the TouchSpotView class

class TouchSpotView : UIView {
   override init(frame: CGRect) {
      super.init(frame: frame)
      backgroundColor = UIColor.lightGray
   }
 
   // Update the corner radius when the bounds change.
   override var bounds: CGRect {
      get { return super.bounds }
      set(newBounds) {
         super.bounds = newBounds
         layer.cornerRadius = newBounds.size.width / 2.0
      }
   }
}

See Also

Advanced Touch Handling

Getting High-Fidelity Input with Coalesced Touches

Learn how to support high-precision touches in your app.

Minimizing Latency with Predicted Touches

Create a smooth and responsive drawing experience using UIKit's predictions for touch locations.