library haptic funciona developer como apple ios swift 3dtouch

ios - haptic - Aplicación 3D touch/Force touch



ios developer library (5)

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

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


Con Swift 4.2 y iOS 12, una posible forma de resolver su problema es crear una subclase personalizada de UIGestureRecognizer que maneje Force Touch y agregarla 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: 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 } } }

Fuentes:


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 UIKit import UIKit.UIGestureRecognizerSubclass import AudioToolbox class DeepPressGestureRecognizer: UIGestureRecognizer { var vibrateOnDeepPress = true var threshold: CGFloat = 0.75 var hardTriggerMinTime: TimeInterval = 0.5 var onDeepPress: (() -> Void)? private var deepPressed: Bool = false { didSet { if (deepPressed && deepPressed != oldValue) { onDeepPress?() } } } private var deepPressedAt: TimeInterval = 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>, with event: UIEvent) { if let touch = touches.first { handle(touch: touch) } } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) { if let touch = touches.first { handle(touch: touch) } } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) { super.touchesEnded(touches, with: event) state = deepPressed ? UIGestureRecognizerState.ended : UIGestureRecognizerState.failed deepPressed = false } private func handle(touch: UITouch) { guard let _ = view, touch.force != 0 && touch.maximumPossibleForce != 0 else { return } let forcePercentage = (touch.force / touch.maximumPossibleForce) let currentTime = Date.timeIntervalSinceReferenceDate if !deepPressed && forcePercentage >= threshold { state = UIGestureRecognizerState.began if vibrateOnDeepPress { AudioServicesPlaySystemSound(k_PeakSoundID) } deepPressedAt = Date.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.perform(hardAction, with: 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(gestureRecognizer: deepPressGestureRecognizer) } func removeDeepPressAction() { guard let gestureRecognizers = gestureRecognizers else { return } for recogniser in gestureRecognizers where recogniser is DeepPressGestureRecognizer { removeGestureRecognizer(gestureRecognizer: recogniser) } } }


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

El 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, a diferencia de un solo evento. Si solo quieres un evento único, puedes ignorar todo en el DFContinuousForceTouchDelegate excepto el - (void) forceTouchRecognized callback.

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 su UIViewController implemente 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 debe agregar DFContinuousForceTouchGestureRecognizer a una vista si se está ejecutando en iOS 8 o menos, ya que usa la nueva propiedad force en UITouch solo está 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.


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

Puede obtener estos toques anulando los toques UIView un UIView de touchesBegan: y los métodos touchesMoved: . No estoy seguro de lo que ve en touchesEnded: todavía.

Si está dispuesto a crear nuevos reconocedores de gestos, tiene acceso completo a UITouch como se expone en UIGestureRecognizerSubclass .

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


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