Article

Handling Input from Apple Pencil

Learn how to detect and respond to touches from Apple Pencil.

Overview

UIKit reports touches from Apple Pencil in the same way it reports touches from the user’s fingers. Specifically, UIKit delivers a UITouch object containing the location of the touch in your app. However, a touch object originating from Apple Pencil contains additional information, including the azimuth and altitude of Apple Pencil and the amount of force recorded at its tip.

Figure 1

Using stylus information as input

An illustration of how azimuth (shown on the left) and altitude (shown on the right) are determined when using Apple Pencil on a screen.

Because Apple Pencil is a separate device, there is a delay between the time Apple Pencil gathers altitude, azimuth, and force values and the time that those values are reported to your app. As a result, UIKit may provide estimated values for those properties initially, and then provide the real values at a later time. If you use altitude, azimuth, or force information from Apple Pencil, you must explicitly handle estimated properties.

Handle Estimated Properties

When UIKit has only an estimate of a property's value, it includes a flag in the estimatedPropertiesExpectingUpdates property of the corresponding UITouch object. When handling a touch event, check that property to determine if you need to update the touch information later.

When a touch object contains estimated properties, UIKit also provides a value in the estimationUpdateIndex property that you can use to identify the touch later. Use the value in the estimationUpdateIndex property as a key to a dictionary that you maintain. Set the value of that key to the app-specific object that you use to store the touch information. When UIKit later reports the real values, use the index to look up your app-specific object and replace the estimated values with the real values.

Listing 1 shows the addSamples method of an app that captures touch data. For each touch, the method creates a custom StrokeSample object with the touch information. If the force value of the touch is only an estimate, the registerForEstimates method caches the sample in a dictionary using the value in the estimationUpdateIndex property as the key.

Listing 1

Tracking touches that need updates

var estimates : [NSNumber : StrokeSample]
 
func addSamples(for touches: [UITouch]) {
   if let stroke = strokeCollection?.activeStroke {
      for touch in touches {
         if touch == touches.last {
            let sample = StrokeSample(point: touch.location(in: self), 
                                 forceValue: touch.force)
            stroke.add(sample: sample)
            registerForEstimates(touch: touch, sample: sample)
         } else {
            let sample = StrokeSample(point: touch.location(in: self), 
                                 forceValue: touch.force, coalesced: true)
            stroke.add(sample: sample)
            registerForEstimates(touch: touch, sample: sample)
         }
      }
      self.setNeedsDisplay()
   }
}
 
func registerForEstimates(touch : UITouch, sample : StrokeSample) {
   if touch.estimatedPropertiesExpectingUpdates.contains(.force) {
      estimates[touch.estimationUpdateIndex!] = sample
   }
}

When UIKit receives the actual values for a touch, it calls the touchesEstimatedPropertiesUpdated(_:) method of your responder or gesture recognizer. Use that method to replace estimated data with the real values provided by UIKit.

Listing 2 shows an example of the touchesEstimatedPropertiesUpdated(_:) method, which updates the force value for the cached StrokeSample objects created in Listing 1. The method uses the value in the estimationUpdateIndex property to retrieve the StrokeSample object from the estimates dictionary. It then updates the force value and removes the sample from the dictionary.

Listing 2

Updating estimated values

override func touchesEstimatedPropertiesUpdated(_ touches: Set<UITouch>) {
   for touch in touches {
      // If the force value is no longer an estimate, update it.
      if !touch.estimatedPropertiesExpectingUpdates.contains(.force) {
         let index = touch.estimationUpdateIndex!
         var sample = estimates[index]
         sample?.force = touch.force
 
         // Remove the key and value from the dictionary.
         estimates.removeValue(forKey: index)
      }
   }
}

Topics

Related Articles

Computing the Perpendicular Force of Apple Pencil

Adjust the force values reported by Apple Pencil so that they are consistent with 3D Touch force values.

See Also

Touches

Handling Touches in Your View

Use touch events directly on a view subclass if touch handling is intricately linked to the view's content.

Tracking the Force of 3D Touch Events

Manipulate your content based on the force of touches.

Leveraging Touch Input for Drawing Apps

Capture touches as a series of strokes and render them efficiently on a drawing canvas.

class UITouch

An object representing the location, size, movement, and force of a touch occurring on the screen.