que framework objective-c ios uiview-hierarchy

objective c - framework - Captura de toques en una subvista fuera del marco de su supervista usando hitTest: withEvent:



uikit ios (7)

Bien, hice algunas excavaciones y pruebas, así es cómo funciona hitTest:withEvent , al menos 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

Diagrama:

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

Como (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 siguiente objeto en la jerarquía.

Sin embargo, si implementa hitTest:withEvent: in (M), los hitTest:withEvent: en cualquier parte de la aplicación se enviarán a (M) (o al menos los conoce). Puede escribir código para manejar el toque en ese caso y devolver el objeto que debería recibir el toque.

Más específicamente: el objetivo de hitTest:withEvent: es devolver el objeto que debería recibir el golpe. Entonces, en (M) podrías escribir 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, por lo que si hay formas más eficientes o correctas de escribir el código, por favor comente.

Espero que eso ayude a cualquiera que responda esta pregunta más tarde. :)

Mi problema: tengo una superview EditView que ocupa básicamente todo el marco de la aplicación, y una subvista MenuView que ocupa solo el ~ 20% inferior, y luego MenuView contiene su propia subvista ButtonView que en realidad reside fuera de los MenuView de MenuView (algo de esta manera: ButtonView.frame.origin.y = -100 ).

(Nota: EditView tiene otras subvistas que no son parte de la MenuView de MenuView de MenuView , pero puede afectar la respuesta).

Probablemente ya conozcas 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 eventos 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

Diagrama:

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

Como (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 siguiente objeto en la jerarquía.

Objetivo: me doy cuenta de que hitTest:withEvent: puede resolver este problema, pero no entiendo exactamente cómo. En mi caso, debería hitTest:withEvent: ser anulado en EditView (mi EditView ''master'')? ¿O debería ser anulado en MenuView , la supervista directa del botón que no está recibiendo toques? ¿O estoy pensando en esto incorrectamente?

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

¡Gracias!


Coloque debajo de las líneas de código en su jerarquía de vista:

- (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 una mayor aclaración, se explicó en mi blog: "goaheadwithiphonetech" con respecto a "Llamada personalizada: el botón no se puede hacer clic en el problema".

Espero que eso te ayude...!!!


En Swift 4

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


He modificado el código de la respuesta aceptada para que sea más genérico: maneja los casos en que la vista recorta las subvistas a sus límites, puede estar oculta y, lo que es más importante, si las subvistas son jerarquías de vistas 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 trate de usar esta solución para casos de uso más complejos.


Lo que haría es tener tanto ButtonView como MenuView en el mismo nivel en la jerarquía de vistas, colocándolos a ambos 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 sus límites.


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 }


Si tiene muchas otras subvistas dentro de su vista principal, probablemente la mayoría de las otras vistas interactivas no funcionarían si utiliza las soluciones anteriores, en ese caso puede usar algo como esto (en Swift 3.2):

class BoundingSubviewsViewExtension: UIView { @IBOutlet var targetView: UIView! override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { // Convert the point to the target view''s coordinate system. // The target view isn''t necessarily the immediate subview let pointForTargetView: CGPoint? = targetView?.convert(point, from: self) if (targetView?.bounds.contains(pointForTargetView!))! { // The target view may have its view hierarchy, // so call its hitTest method to return the right hit-test view return targetView?.hitTest(pointForTargetView ?? CGPoint.zero, with: event) } return super.hitTest(point, with: event) } }