Récupérer UIButton tapé dans une animation en cours d'exécution qui peut être légèrement hors de l'écran

Voici ce que le code (ci-dessous) peut ressembler:

entrez la description de l'image ici

code:

import UIKit class TornadoButton: UIButton{ override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let pres = self.layer.presentation()! let suppt = self.convert(point, to: self.superview!) let prespt = self.superview!.layer.convert(suppt, to: pres) return super.hitTest(prespt, with: event) } } class TestViewController: UIViewController{ override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let greySubview = UIView() greySubview.backgroundColor = .red greySubview.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(greySubview) greySubview.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true greySubview.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true greySubview.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true greySubview.heightAnchor.constraint(equalTo: greySubview.widthAnchor).isActive = true let button = TornadoButton() greySubview.addSubview(button) button.translatesAutoresizingMaskIntoConstraints = false button.widthAnchor.constraint(equalTo: greySubview.widthAnchor, multiplier: 0.09).isActive = true button.heightAnchor.constraint(equalTo: greySubview.heightAnchor, multiplier: 0.2).isActive = true //below constrains are needed, else the origin of the UIBezierPath is wrong button.centerXAnchor.constraint(equalTo: greySubview.centerXAnchor).isActive = true button.centerYAnchor.constraint(equalTo: greySubview.centerYAnchor, constant: self.view.frame.height * 0.35).isActive = true self.view.layoutIfNeeded() let orbit = CAKeyframeAnimation(keyPath: "position") button.addTarget(self, action: #selector(tappedOnCard(_:)), for: .touchUpInside) let circlePath = UIBezierPath(arcCenter: greySubview.frame.origin, radius: CGFloat(greySubview.frame.width * 0.5), startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true) orbit.duration = 12 orbit.path = circlePath.cgPath orbit.isAdditive = true orbit.repeatCount = Float.greatestFiniteMagnitude orbit.calculationMode = kCAAnimationPaced orbit.rotationMode = kCAAnimationRotateAuto button.layer.add(orbit, forKey: "orbit") button.backgroundColor = .blue let gr = UITapGestureRecognizer(target: self, action: #selector(onTap(gesture:))) greySubview.addGestureRecognizer(gr) } @objc func onTap(gesture:UITapGestureRecognizer) { let p = gesture.location(in: gesture.view) let v = gesture.view?.hitTest(p, with: nil) } @IBAction func tappedOnCard(_ sender: UIButton){ print(sender) } } 

Cela fonctionne presque, MAIS:

Je peux récupérer le button si le button est 100% visible sur l'écran. Si elle est, par exemple, visible à 50% (et 50% de l'écran (regardez l'image ci-dessous)), je ne récupère pas les buttons en tapant (alias pas récupérer l'printing).

Comment puis-je récupérer le button alors qu'il est dans une animation en cours d'exécution et il peut être légèrement hors de l'écran?

entrez la description de l'image ici

Réponse originale:

Il est très probable que l'UITapGestureRecognizer dans greySubview consum le contact et ne le laisse pas passer au button. Essaye ça:

 gr.cancelsTouchesInView = NO; 

avant d'append UITapGestureRecognizer à greySubview.


Réponse éditée:

Veuillez ignorer ma réponse précédente et annuler le changement si vous l'avez fait. Ma réponse originale n'a pas de sens car dans votre cas, le button est la sous-vue de grayView et cancelsTouchesInView fonctionne vers le haut dans la hiérarchie de la vue, pas vers le bas.

Après beaucoup de fouilles, j'ai été capable de comprendre pourquoi cela se passait. C'était à cause de tests de hit incorrects dans le TornadoButton pendant l'animation. Puisque vous animez le button, vous devrez replace les hitTest et pointInside du TornadoButton afin qu'elles tiennent count de la position animée au lieu de la position réelle du button lors du test de hit.

L'implémentation par défaut de la méthode pointInside ne prend pas en count la couche de présentation animée du button, et essaie juste de vérifier le sharepoint contact dans le rect (qui échouera parce que le toucher était ailleurs).

Les implémentations suivantes des methods semblent faire l'affaire:

 class TornadoButton: UIButton{ override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let pres = self.layer.presentation()! let suppt = self.convert(point, to: self.superview!) let prespt = self.superview!.layer.convert(suppt, to: pres) if (pres.hitTest(suppt)) != nil{ return self } return super.hitTest(prespt, with: event) } override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { let pres = self.layer.presentation()! let suppt = self.convert(point, to: self.superview!) return (pres.hitTest(suppt)) != nil } } 

Je ne comprends pas pourquoi vous utilisez des buttons pour afficher un rectangle simple lorsque les vues fonctionnent aussi, mais de toute façon … Vous n'avez pas montré où vous ajoutez la cible à votre button.

 button.addTarget(self, action: #selector(ViewController.buttonPressed(_:)), for: .touchUpInside) // This is needed that your button 'does something' button.tag = 1 // This is a tag that you can use to identify which button has been pressed 

avec la buttonPressed fonctionAppuyez buttonPressed quelque chose comme ceci:

 @objc func buttonPressed(_ button: UIButton) { switch button.tag { case 1: print("First button was pressed") case 2: print("Second button was pressed") default: print("I don't know the button you pressed") } }