Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

3D touch/Force touch implementation

Tags:

ios

swift

3dtouch

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?

like image 279
Avi Rok Avatar asked Sep 24 '15 22:09

Avi Rok


People also ask

Is Force Touch the same as 3D Touch?

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.

Is Apple going to bring back Force Touch?

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.

When was Force Touch introduced?

In fact, Force Touch support has been a part of Android for years; it was introduced in Android 1.0.


3 Answers

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).

like image 70
random Avatar answered Oct 20 '22 02:10

random


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.

like image 25
Rhythmic Fistman Avatar answered Oct 20 '22 02:10

Rhythmic Fistman


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

like image 9
Imanou Petit Avatar answered Oct 20 '22 00:10

Imanou Petit