How can we implement 3D touch to check if the user taps on UIView
or force touch on UIView
?
Is there a way to do this with UIGestureRecognize
or only with UITouch
?
Simply put, Force Touch has less capability to measure your touches and presses and doesn't react as quickly to your input, whereas the new 3D Touch is highly sensitive and reacts immediately, while also allowing different "levels" of actions based on how firmly you press.
Now, Apple is said to be bringing back 3D touch on Apple Watch, iPhone, and MacBooks. According to a recent report, Apple has secured patents for next-generation force-pressure sensors. Earlier this year, there were reports suggesting that Apple may replace the Crown on the Apple Watch with new optical sensors.
In fact, Force Touch support has been a part of Android for years; it was introduced in Android 1.0.
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 UIKit
class 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:
GitHub / FlexMonkey - DeepPressGestureRecognizer
GitHub / ashleymills - ForceTouchGestureRecognizer
Apple Developer Documentation - Implementing a Discrete Gesture Recognizer
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With