Pregunta Aplicación 3D touch / Force touch


¿Cómo podemos implementar 3D touch para verificar si el usuario toca UIView o forzar el toque en UIView?

¿Hay alguna manera de hacer esto con UIGestureRecognize o solo con UITouch?


32
2017-09-24 22:27


origen


Respuestas:


Puede hacerlo sin un reconocedor de gestos designado. No necesita ajustar el método touchesEnded y touchesBegan, sino simplemente los toquesMoved para obtener los valores correctos. obtener la fuerza de un uitouch desde el principio / terminado devolverá valores extraños.

UITouch *touch = [touches anyObject];

CGFloat maximumPossibleForce = touch.maximumPossibleForce;
CGFloat force = touch.force;
CGFloat normalizedForce = force/maximumPossibleForce;

luego, establezca un umbral de fuerza y ​​compare la Fuerza normalizada con este umbral (0,75 parece estar bien para mí).


17
2017-10-06 12:52



Las propiedades 3D Touch están disponibles en UITouch objetos.

Puede obtener estos toques anulando una UIViewes touchesBegan: y touchesMoved: métodos. No estoy seguro de lo que ves en touchesEnded: todavía.

Si está dispuesto a crear nuevos reconocedores de gestos, tiene acceso completo a la UITouches como expuesto en UIGestureRecognizerSubclass.

No estoy seguro de cómo podría usar las propiedades táctiles en 3D de forma tradicional UIGestureRecognizer. Quizás a través del UIGestureRecognizerDelegate protocolo gestureRecognizer:shouldReceiveTouch: método.


8
2017-09-25 01:02



La forma en que lo hago es usar una combinación de UITapGestureRecognizer (proporcionado por Apple) y un DFContinuousForceTouchGestureRecognizer (proporcionado por mí).

los DFContinuousForceTouchGestureRecognizer es bueno porque proporciona actualizaciones continuas sobre los cambios de presión para que pueda hacer cosas como aumentar la vista a medida que el usuario varía su presión sobre ella, en lugar de un solo evento. Si solo quieres un evento único, puedes ignorar todo en el DFContinuousForceTouchDelegate excepto el - (void) forceTouchRecognized llamar de vuelta.

https://github.com/foggzilla/DFContinuousForceTouchGestureRecognizer

Puede descargar esto y ejecutar la aplicación de muestra en un dispositivo que admita fuerza presione para ver cómo se siente.

En tus UIViewController implementar lo siguiente:

- (void)viewDidLoad {
    [super viewDidLoad];
    _forceTouchRecognizer = [[DFContinuousForceTouchGestureRecognizer alloc] init];
    _forceTouchRecognizer.forceTouchDelegate = self;

    //here to demonstrate how this works alonside a tap gesture recognizer
    _tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];

    [self.imageView addGestureRecognizer:_tapGestureRecognizer];
    [self.imageView addGestureRecognizer:_forceTouchRecognizer];
}

implementar selector para gesto de toque

#pragma UITapGestureRecognizer selector

- (void)tapped:(id)sender {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [[[UIAlertView alloc] initWithTitle:@"Tap" message:@"YEAH!!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
    });
}

Implementar el protocolo delegado para force touch:

#pragma DFContinuousForceTouchDelegate

- (void)forceTouchRecognized:(DFContinuousForceTouchGestureRecognizer *)recognizer {
    self.imageView.transform = CGAffineTransformIdentity;
    [self.imageView setNeedsDisplay];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [[[UIAlertView alloc] initWithTitle:@"Force Touch" message:@"YEAH!!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
    });
}

- (void)forceTouchRecognizer:(DFContinuousForceTouchGestureRecognizer *)recognizer didStartWithForce:(CGFloat)force maxForce:(CGFloat)maxForce {
    CGFloat transformDelta = 1.0f + ((force/maxForce) / 3.0f);
    self.imageView.transform = CGAffineTransformMakeScale(transformDelta, transformDelta);
    [self.imageView setNeedsDisplay];
}

- (void) forceTouchRecognizer:(DFContinuousForceTouchGestureRecognizer *)recognizer didMoveWithForce:(CGFloat)force maxForce:(CGFloat)maxForce {
    CGFloat transformDelta = 1.0f + ((force/maxForce) / 3.0f);
    self.imageView.transform = CGAffineTransformMakeScale(transformDelta, transformDelta);
    [self.imageView setNeedsDisplay];
}

- (void)forceTouchRecognizer:(DFContinuousForceTouchGestureRecognizer *)recognizer didCancelWithForce:(CGFloat)force maxForce:(CGFloat)maxForce  {
    self.imageView.transform = CGAffineTransformIdentity;
    [self.imageView setNeedsDisplay];
}

- (void)forceTouchRecognizer:(DFContinuousForceTouchGestureRecognizer *)recognizer didEndWithForce:(CGFloat)force maxForce:(CGFloat)maxForce  {
    self.imageView.transform = CGAffineTransformIdentity;
    [self.imageView setNeedsDisplay];
}

- (void)forceTouchDidTimeout:(DFContinuousForceTouchGestureRecognizer *)recognizer {
    self.imageView.transform = CGAffineTransformIdentity;
    [self.imageView setNeedsDisplay];
}

Tenga en cuenta que esto solo será útil en un dispositivo que admita fuerza táctil.

Además, no deberías agregar el DFContinuousForceTouchGestureRecognizer para ver si está ejecutando en iOS 8 o menos, ya que utiliza el nuevo force propiedad en UITouch solo disponible en iOS 9.

Si agrega esto en iOS 8, se bloqueará, por lo tanto, agregue este reconocedor de manera condicional en función de la versión de iOS en la que esté ejecutando si admite versiones anteriores a iOS 9.


5
2017-09-30 17:36



Creé un UIGestureRecognizer que emula el comportamiento de la aplicación Apple Mail. Con el toque tridimensional, comienza con una breve vibración de pulso simple y luego una acción secundaria opcional (hardTarget) y un pulso llamado al presionar con fuerza poco después de la presión inicial.

Adaptado de https://github.com/FlexMonkey/DeepPressGestureRecognizer

Cambios:

  • 3D táctil vibra pulsos como el comportamiento del sistema iOS
  • debe tocarse para que finalice, como la aplicación de correo de Apple
  • el umbral predeterminado es el nivel predeterminado del sistema
  • el toque duro activa la llamada de hardAction como aplicación de correo

Nota: Agregué el sonido del sistema indocumentado k_PeakSoundID, pero no dude en apagarlo si no se siente cómodo usando una constante más allá del rango documentado. He estado usando sonidos del sistema con constantes no reveladas durante años, pero se te invita a que apagues los pulsos de vibración usando la propiedad vibrateOnDeepPress.

import AudioToolbox
import UIKit.UIGestureRecognizerSubclass

class DeepPressGestureRecognizer: UIGestureRecognizer
{
    var vibrateOnDeepPress = true
    var threshold:CGFloat = 0.75
    var hardTriggerMinTime:NSTimeInterval = 0.5

    private var deepPressed: Bool = false
    private var deepPressedAt: NSTimeInterval = 0
    private var k_PeakSoundID:UInt32 = 1519
    private var hardAction:Selector?
    private var target: AnyObject?

    required init(target: AnyObject?, action: Selector, hardAction:Selector?=nil, threshold: CGFloat = 0.75)
    {
        self.target = target
        self.hardAction = hardAction
        self.threshold = threshold

        super.init(target: target, action: action)
    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent)
    {
        if let touch = touches.first
        {
            handleTouch(touch)
        }
    }

    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent)
    {
        if let touch = touches.first
        {
            handleTouch(touch)
        }
    }

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent)
    {
        super.touchesEnded(touches, withEvent: event)

        state = deepPressed ? UIGestureRecognizerState.Ended : UIGestureRecognizerState.Failed

        deepPressed = false
    }

    private func handleTouch(touch: UITouch)
    {
        guard let _ = view where touch.force != 0 && touch.maximumPossibleForce != 0 else
        {
            return
        }

        let forcePercentage = (touch.force / touch.maximumPossibleForce)
        let currentTime = NSDate.timeIntervalSinceReferenceDate()

        if !deepPressed && forcePercentage >= threshold
        {
            state = UIGestureRecognizerState.Began

            if vibrateOnDeepPress
            {
                AudioServicesPlaySystemSound(k_PeakSoundID)
            }

            deepPressedAt = NSDate.timeIntervalSinceReferenceDate()
            deepPressed = true
        }
        else if deepPressed && forcePercentage <= 0
        {
            endGesture()
        }
        else if deepPressed && currentTime - deepPressedAt > hardTriggerMinTime && forcePercentage == 1.0
        {
            endGesture()

            if vibrateOnDeepPress
            {
                AudioServicesPlaySystemSound(k_PeakSoundID)
            }

            //fire hard press
            if let hardAction = self.hardAction, let target = self.target {
                target.performSelector(hardAction, withObject: self)
            }
        }
    }

    func endGesture() {
        state = UIGestureRecognizerState.Ended
        deepPressed = false
    }
}

// MARK: DeepPressable protocol extension
protocol DeepPressable
{
    var gestureRecognizers: [UIGestureRecognizer]? {get set}

    func addGestureRecognizer(gestureRecognizer: UIGestureRecognizer)
    func removeGestureRecognizer(gestureRecognizer: UIGestureRecognizer)

    func setDeepPressAction(target: AnyObject, action: Selector)
    func removeDeepPressAction()
}

extension DeepPressable
{
    func setDeepPressAction(target: AnyObject, action: Selector)
    {
        let deepPressGestureRecognizer = DeepPressGestureRecognizer(target: target, action: action, threshold: 0.75)

        self.addGestureRecognizer(deepPressGestureRecognizer)
    }

    func removeDeepPressAction()
    {
        guard let gestureRecognizers = gestureRecognizers else
        {
            return
        }

        for recogniser in gestureRecognizers where recogniser is DeepPressGestureRecognizer
        {
            removeGestureRecognizer(recogniser)
        }
    }
}

3
2017-07-14 20:21



Con Swift 4.2 y iOS 12, una posible forma de resolver su problema es crear una subclase personalizada de UIGestureRecognizer que maneja Force Touch y lo agrega a su vista junto a un UITapGestureRecognizer. El siguiente código completo muestra cómo implementarlo:

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: 200).isActive = true
        redView.heightAnchor.constraint(equalToConstant: 200).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

final class ForceTouchGestureRecognizer: UIGestureRecognizer {

    private let threshold: CGFloat = 0.75

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        if let touch = touches.first {
            handleTouch(touch)
        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        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
        }
    }

}

Fuentes:


0
2017-08-07 09:06