3D touch/Force touch implementation 3D touch/Force touch implementation ios ios

3D touch/Force touch implementation


You can do it without a designated gesture recognizer. You do not need to adjust the touchesEnded and touchesBegan method, but simply the touchesMoved to obtain the correct values. getting the force of a uitouch from began/ended will return weird values.

UITouch *touch = [touches anyObject];CGFloat maximumPossibleForce = touch.maximumPossibleForce;CGFloat force = touch.force;CGFloat normalizedForce = force/maximumPossibleForce;

then, set a force threshold and compare the normalizedForce to this threshold (0.75 seems fine for me).


The 3D Touch properties are available on UITouch objects.

You can get these touches by overriding a UIView's touchesBegan: and touchesMoved: methods. Not sure what you see in touchesEnded: yet.

If you're willing to create new gesture recognizers, you have full access to the UITouches as exposed in UIGestureRecognizerSubclass.

I'm not sure how you could use the 3D touch properties in a traditional UIGestureRecognizer. Maybe via the UIGestureRecognizerDelegate protocol's gestureRecognizer:shouldReceiveTouch: method.


With Swift 4.2 and iOS 12, a possible way to solve your problem is to create a custom subclass of UIGestureRecognizer that handles Force Touch and add it to your view next to a UITapGestureRecognizer. The following complete code shows how to implement it:

ViewController.swift

import UIKitclass ViewController: UIViewController {    let redView = UIView()    lazy var tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapHandler))    lazy var forceTouchGestureRecognizer = ForceTouchGestureRecognizer(target: self, action: #selector(forceTouchHandler))    override func viewDidLoad() {        super.viewDidLoad()        redView.backgroundColor = .red            redView.addGestureRecognizer(tapGestureRecognizer)        view.addSubview(redView)        redView.translatesAutoresizingMaskIntoConstraints = false        redView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true        redView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true        redView.widthAnchor.constraint(equalToConstant: 100).isActive = true        redView.heightAnchor.constraint(equalToConstant: 100).isActive = true    }    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {        super.traitCollectionDidChange(previousTraitCollection)        if traitCollection.forceTouchCapability == UIForceTouchCapability.available {            redView.addGestureRecognizer(forceTouchGestureRecognizer)        } else  {            // When force touch is not available, remove force touch gesture recognizer.            // Also implement a fallback if necessary (e.g. a long press gesture recognizer)            redView.removeGestureRecognizer(forceTouchGestureRecognizer)        }    }    @objc func tapHandler(_ sender: UITapGestureRecognizer) {        print("Tap triggered")    }    @objc func forceTouchHandler(_ sender: ForceTouchGestureRecognizer) {        UINotificationFeedbackGenerator().notificationOccurred(.success)        print("Force touch triggered")    }}

ForceTouchGestureRecognizer.swift

import UIKit.UIGestureRecognizerSubclass@available(iOS 9.0, *)final class ForceTouchGestureRecognizer: UIGestureRecognizer {    private let threshold: CGFloat = 0.75    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {        super.touchesBegan(touches, with: event)        if let touch = touches.first {            handleTouch(touch)        }    }    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {        super.touchesMoved(touches, with: event)        if let touch = touches.first {            handleTouch(touch)        }    }    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {        super.touchesEnded(touches, with: event)        state = UIGestureRecognizer.State.failed    }    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {        super.touchesCancelled(touches, with: event)        state = UIGestureRecognizer.State.failed    }    private func handleTouch(_ touch: UITouch) {        guard touch.force != 0 && touch.maximumPossibleForce != 0 else { return }        if touch.force / touch.maximumPossibleForce >= threshold {            state = UIGestureRecognizer.State.recognized        }    }}

Sources: