Creating a 3D Touch gesture recognizer

In this post I will explain how to create a simple view that gives visual feedback when firmly pressed on a device with 3D touch capabilities like the iPhone 6s and 7. Additionally, it will give the same visual feedback on devices that don’t support 3D touch, using a long press gesture recognizer. A demonstration of the end result can be seen in the video below.

3D Touch was introduced in iOS 9 for devices with the hardware capabilities to make use of it: the iPhone 6s and now the iPhone 7. The UITouch class introduced two new properties:
– force (a CGFloat)
– maximumPossibleForce (also a CGFloat)

In the documentation, ‘force’ is described as the force of the touch where a value of 1.0 represents the force of an average touch. This means that a force above 1.0 could be interpreted as a ‘force touch’: the user presses on the screen instead of just tapping.

The ‘maximumPossibleForce’ property is (at least in practice) the maximum force that the system will register. On an iPhone 6s this comes down to 6,667. If you want to learn more about this property I can recommend this great post by R. Kevin Nelson.

The most convenient way of using the 3D Touch capabilities is to write a custom UIGestureRecognizer. For now, UIKit doesn’t provide a ‘3DTouchGestureRecognizer’ subclass like it does for long press or rotation gestures. This means we have to write it ourselves.

We can approach this in multiple ways. Our first option is to give the gesture recognizer a property called ‘forceNeededToFireGesture’ which we can set with a value between 1 and 6,667. As soon as the force reaches the set value, we will change the .state property. This will subsequently send the selector message that the gesture recognizer was initialized with:

Although useful for certain cases, the disadvantage of this approach is that we can’t easily gain the force that the user applies during the gesture. We will need this to give visual feedback to the user while using the interface element.

The solution is to change the way the custom gesture recognizer behaves. Instead of setting the state property only once when the needed force is reached, we set it as soon as anything happens. Just before setting the state, we set a custom forceValue variable with the force of the registered touch:

Creating the visual feedback

It’s important to give the user some feedback about what’s happening when the element is pressed firmly. In this example app, our UIView will ‘grow’ steadily along with the force applied. The outline of the original shape gets a little bit darker as soon as it is touched. This effect can be achieved in multiple ways. For this example I’ve made a sublayer (called feedbackLayer) that’s placed under the top layer. Every time the selector method is called, a scale transform is applied to this feedbackLayer. The scale amount follows the amount of force applied, until a certain threshold (90% of maximumPossibleForce) is reached:

Provide taptic feedback

Until recently there wasn’t a public API available that we could use to give the user taptic feedback when needed. To get the familiar haptic ‘tic’ we can use a private API:

With the introduction of iOS 10 there is a new API called UIFeedbackGenerator. You can read all about it in this interesting post. However, at the moment of writing, only the new taptic engine found in the iPhone 7 supports this API. It is ignored on the iPhone 6s.

Making an alternative visual feedback for unsupported devices

For devices that don’t support 3D Touch I’ve created a long press gesture recognizer as an alternative to the 3D touch behavior. In this case, the ‘growing’ feedback layer isn’t controlled by the applied force, but by the time the user presses down on the view:

Responding to changes in 3D touch capability

Even when the used device supports 3D Touch it is possible for the user to switch it off in the accessibility settings. When the user changes the 3D Touch setting, the app has to respond by switching to an alternative gesture.

We can achieve this by overriding the ‘traitCollectionDidChange’ method in our ViewController. This method will be called when the user enables or disables the 3D touch function.

The final project can be downloaded from GitHub.