Capturando toques en una subvista fuera del marco de su supervisión usando hitTest: withEvent:

Mi problema: tengo un SupervView EditView que ocupa básicamente todo el marco de la aplicación, y una subvista MenuView que ocupa solo el background ~ 20%, y luego MenuView contiene su propia subvista ButtonView que realmente reside fuera de los MenuView de MenuView (algo así: ButtonView.frame.origin.y = -100 ).

(nota: EditView tiene otras subvistas que no son parte de la MenuView de la vista de MenuView , pero pueden afectar a la respuesta).

Probablemente ya sepas el problema: cuando ButtonView está dentro de los límites de MenuView (o, más específicamente, cuando mis toques están dentro de los límites de ButtonView ), ButtonView responde a los events táctiles. Cuando mis toques están fuera de los MenuView de MenuView (pero aún dentro de los límites de ButtonView ), ButtonView no recibe ningún evento táctil.

Ejemplo:

  • (E) es EditView , el padre de todas las vistas
  • (M) es MenuView , una subvista de EditView
  • (B) es ButtonView , una subvista de MenuView

Diagtwig:

 +------------------------------+ |E | | | | | | | | | |+-----+ | ||B | | |+-----+ | |+----------------------------+| ||M || || || |+----------------------------+| +------------------------------+ 

Debido a que (B) está fuera del marco (M), un toque en la región (B) nunca se enviará a (M); de hecho, (M) nunca analiza el toque en este caso y el toque se envía a El próximo object en la jerarquía.

Objetivo: recojo que hitTest:withEvent: puede resolver este problema, pero no entiendo exactamente cómo. En mi caso, debería hitTest:withEvent: se EditView en EditView (mi EditView 'maestra')? ¿O debería MenuView en MenuView , la supervisión directa del button que no recibe toques? ¿O estoy pensando en esto incorrectamente?

Si esto requiere una larga explicación, un buen recurso en línea sería útil, excepto los documentos UIView de Apple, que no me lo han aclarado.

¡Gracias!

He modificado el código de la respuesta aceptada para que sea más genérico: maneja los casos en los que la vista recorta las subvenciones a sus límites, puede estar oculto y, lo que es más importante: si las subvistas son jerarquías de vista complejas, se devolverá la subvista correcta.

 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (self.clipsToBounds) { return nil; } if (self.hidden) { return nil; } if (self.alpha == 0) { return nil; } for (UIView *subview in self.subviews.reverseObjectEnumerator) { CGPoint subPoint = [subview convertPoint:point fromView:self]; UIView *result = [subview hitTest:subPoint withEvent:event]; if (result) { return result; } } return nil; } 

SWIFT 3

 override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if clipsToBounds || isHidden || alpha == 0 { return nil } for subview in subviews.reversed() { let subPoint = subview.convert(point, from: self) if let result = subview.hitTest(subPoint, with: event) { return result } } return nil } 

Espero que esto ayude a cualquiera que intente usar esta solución para casos de uso más complejos.

Ok, hice algunas excavaciones y testings, así es como hitTest:withEvent funciona, al less en un nivel alto. Imagen de este escenario:

  • (E) es EditView , el padre de todas las vistas
  • (M) es MenuView , una subvista de EditView
  • (B) es ButtonView , una subvista de MenuView

Diagtwig:

 +------------------------------+ |E | | | | | | | | | |+-----+ | ||B | | |+-----+ | |+----------------------------+| ||M || || || |+----------------------------+| +------------------------------+ 

Debido a que (B) está fuera del marco (M), un toque en la región (B) nunca se enviará a (M); de hecho, (M) nunca analiza el toque en este caso y el toque se envía a El próximo object en la jerarquía.

Sin embargo, si implementa hitTest:withEvent: in (M), los hitTest:withEvent: en cualquier lugar de la aplicación se enviarán a (M) (o al less sabrá de ellos). Puede escribir código para manejar el toque en ese caso y devolver el object que debe recibir el toque.

Más específicamente: el objective de hitTest:withEvent: es devolver el object que debería recibir el golpe. Entonces, en (M) podrías escribir un código como este:

 // need this to capture button taps since they are outside of self.frame - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { for (UIView *subview in self.subviews) { if (CGRectContainsPoint(subview.frame, point)) { return subview; } } // use this to pass the 'touch' onward in case no subviews trigger the touch return [super hitTest:point withEvent:event]; } 

Todavía soy muy nuevo en este método y este problema, así que si hay forms más eficientes o correctas de escribir el código, comenta.

Espero que ayude a alguien más que llegue a esta pregunta más tarde. 🙂

En Swift3

 override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if !clipsToBounds && !isHidden && alpha > 0 { for member in subviews.reversed() { let subPoint = member.convert(point, from: self) if let result = member.hitTest(subPoint, with: event) { return result } } } return nil } 

Lo que haría sería que tanto el ButtonView como el MenuView existieran en el mismo nivel en la jerarquía de la vista colocándolos en un contenedor cuyo marco se ajuste completamente a ambos. De esta manera, la región interactiva del elemento recortado no se ignorará debido a los límites de su supervisión.

Si alguien lo necesita, aquí está la alternativa rápida

 override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { if !self.clipsToBounds && !self.hidden && self.alpha > 0 { for subview in self.subviews.reverse() { let subPoint = subview.convertPoint(point, fromView:self); if let result = subview.hitTest(subPoint, withEvent:event) { return result; } } } return nil } 

Coloque a continuación las líneas de código en su jerarquía de vistas:

 - (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { UIView* hitView = [super hitTest:point withEvent:event]; if (hitView != nil) { [self.superview bringSubviewToFront:self]; } return hitView; } - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event { CGRect rect = self.bounds; BOOL isInside = CGRectContainsPoint(rect, point); if(!isInside) { for (UIView *view in self.subviews) { isInside = CGRectContainsPoint(view.frame, point); if(isInside) break; } } return isInside; } 

Para mayor aclaración, se explicó en mi blog: "goaheadwithiphonetech" con respecto a "Llamada personalizada: El button no es un problema que se pueda hacer clic".

Espero que eso te ayude…!!!