Gestion des événements pour iOS – comment hitTest: withEvent: et pointInside: withEvent: sont liés?

Bien que la plupart des documents Apple soient très bien écrits, je pense que « Guide de gestion des événements pour iOS » est une exception. Il m’est difficile de comprendre clairement ce qui a été décrit ici.

Le document dit:

Dans les tests de hit, une fenêtre appelle hitTest:withEvent: dans la vue la plus haute de la hiérarchie des vues; cette méthode procède en appelant récursivement pointInside:withEvent: sur chaque vue de la hiérarchie de vues qui renvoie YES, en descendant la hiérarchie jusqu’à ce qu’elle trouve la sous-vue dans les limites de laquelle le contact a eu lieu. Cette vue devient la vue de test de hit.

Alors, est-ce que seul hitTest:withEvent: de la vue la plus haute est appelé par le système, qui appelle pointInside:withEvent: de toutes les sous-vues, et si le retour d’une sous-vue spécifique est OUI, appelle alors pointInside:withEvent: des sous-classes de cette sous-vue?

Cela semble être une question fondamentale. Mais je suis d’accord avec vous, le document n’est pas aussi clair que d’autres documents, alors voici ma réponse.

L’implémentation de hitTest:withEvent: dans UIResponder effectue les opérations suivantes:

  • Il appelle le pointInside:withEvent: of self
  • Si le retour est NON, hitTest:withEvent: renvoie nil . la fin de l’histoire.
  • Si le retour est OUI, il envoie des hitTest:withEvent: à ses sous-vues. il commence à partir de la sous-vue de niveau supérieur et continue vers d’autres vues jusqu’à ce qu’une sous-vue retourne un object non nil ou que toutes les sous-vues reçoivent le message.
  • Si une sous-vue renvoie un object non nil la première fois, le premier hitTest:withEvent: renvoie cet object. la fin de l’histoire.
  • Si aucune sous-vue ne renvoie un object non nil , le premier hitTest:withEvent: renvoie self

Ce processus se répète de manière récursive, de sorte que la vue feuille de la hiérarchie de vues est normalement renvoyée.

Cependant, vous pouvez remplacer hitTest:withEvent pour faire quelque chose différemment. Dans de nombreux cas, pointInside:withEvent: est plus simple et fournit toujours suffisamment d’options pour modifier la gestion des événements dans votre application.

Je pense que vous confondez le sous-classement avec la hiérarchie des vues. Ce que dit le doc est comme suit. Disons que vous avez cette hiérarchie de vue. Par hiérarchie, je ne parle pas de la hiérarchie des classes, mais des vues dans la hiérarchie des vues, comme suit:

 +----------------------------+ |A | |+--------+ +------------+ | ||B | |C | | || | |+----------+| | |+--------+ ||D || | | |+----------+| | | +------------+ | +----------------------------+ 

Dis que tu mets ton doigt dans D Voici ce qui va arriver:

  1. hitTest:withEvent: est appelé sur A , la vue la plus haute de la hiérarchie de vues.
  2. pointInside:withEvent: est appelé récursivement sur chaque vue.
    1. pointInside:withEvent: est appelé sur A et renvoie YES
    2. pointInside:withEvent: est appelé sur B et renvoie NO
    3. pointInside:withEvent: est appelé sur C et renvoie YES
    4. pointInside:withEvent: est appelé sur D et renvoie YES
  3. Sur les vues qui ont renvoyé YES , la hiérarchie verra la sous-vue sur laquelle le toucher a eu lieu. Dans ce cas, à partir de A , C et D , ce sera D
  4. D sera la vue du hit-test

Je trouve que ce test Hit dans iOS est très utile

entrer la description de l'image ici

 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) { return nil; } if ([self pointInside:point withEvent:event]) { for (UIView *subview in [self.subviews reverseObjectEnumerator]) { CGPoint convertedPoint = [subview convertPoint:point fromView:self]; UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event]; if (hitTestView) { return hitTestView; } } return self; } return nil; } 

Modifier Swift 4:

 override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if self.point(inside: point, with: event) { return super.hitTest(point, with: event) } guard isUserInteractionEnabled, !isHidden, alpha > 0 else { return nil } for subview in subviews.reversed() { let convertedPoint = subview.convert(point, from: self) if let hitView = subview.hitTest(convertedPoint, with: event) { return hitView } } return nil } 

Merci pour les réponses, ils m’ont aidé à résoudre la situation avec des vues “superposées”.

 +----------------------------+ |A +--------+ | | |B +------------------+ | | | |CX | | | | +------------------+ | | | | | | +--------+ | | | +----------------------------+ 

Supposons que l’utilisateur touche X pointInside:withEvent: on B renvoie NO , donc hitTest:withEvent: renvoie A J’ai écrit la catégorie sur UIView pour gérer le problème lorsque vous devez recevoir le contact sur la vue la plus visible .

 - (UIView *)overlapHitTest:(CGPoint)point withEvent:(UIEvent *)event { // 1 if (!self.userInteractionEnabled || [self isHidden] || self.alpha == 0) return nil; // 2 UIView *hitView = self; if (![self pointInside:point withEvent:event]) { if (self.clipsToBounds) return nil; else hitView = nil; } // 3 for (UIView *subview in [self.subviewsreverseObjectEnumerator]) { CGPoint insideSubview = [self convertPoint:point toView:subview]; UIView *sview = [subview overlapHitTest:insideSubview withEvent:event]; if (sview) return sview; } // 4 return hitView; } 
  1. Nous ne devons pas envoyer d’événements tactiles pour des vues masquées ou transparentes ou des vues avec userInteractionEnabled défini sur NO ;
  2. Si le toucher est à self intérieur de self , le self sera considéré comme un résultat potentiel.
  3. Vérifiez récursivement toutes les sous-vues de hit. Le cas échéant, renvoyez-le.
  4. Autrement, retournez vous-même ou zéro selon le résultat de l’étape 2.

Remarque: [self.subviewsreverseObjectEnumerator] devait suivre la hiérarchie de vue du haut vers le bas. Et vérifiez les clipsToBounds pour vous assurer de ne pas tester les sous-vues masquées.

Usage:

  1. Importez la catégorie dans votre vue sous-classée.
  2. Remplacez hitTest:withEvent: avec ceci
 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { return [self overlapHitTest:point withEvent:event]; } 

Le Guide officiel d’Apple fournit également de bonnes illustrations.

J’espère que cela aide quelqu’un.

Cela se voit comme cet extrait!

 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01) { return nil; } if (![self pointInside:point withEvent:event]) { return nil; } __block UIView *hitView = self; [self.subViews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { CGPoint thePoint = [self convertPoint:point toView:obj]; UIView *theSubHitView = [obj hitTest:thePoint withEvent:event]; if (theSubHitView != nil) { hitView = theSubHitView; *stop = YES; } }]; return hitView; } 

L’extrait de @lion fonctionne comme un charme. Je l’ai porté sur swift 2.1 et l’ai utilisé comme une extension d’UIView. Je le poste ici au cas où quelqu’un en aurait besoin.

 extension UIView { func overlapHitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { // 1 if !self.userInteractionEnabled || self.hidden || self.alpha == 0 { return nil } //2 var hitView: UIView? = self if !self.pointInside(point, withEvent: event) { if self.clipsToBounds { return nil } else { hitView = nil } } //3 for subview in self.subviews.reverse() { let insideSubview = self.convertPoint(point, toView: subview) if let sview = subview.overlapHitTest(insideSubview, withEvent: event) { return sview } } return hitView } } 

Pour l’utiliser, il suffit de remplacer hitTest: point: withEvent dans votre uiview comme suit:

 override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { let uiview = super.hitTest(point, withEvent: event) print("hittest",uiview) return overlapHitTest(point, withEvent: event) }