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 UITouch
es 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: