iphone - inicio - ¿Cómo puedo hacer clic en un botón detrás de una UIView transparente?
como desactivar el boton home del iphone (13)
Últimamente escribí una clase que me ayudará con eso. Utilizándolo como una clase personalizada para un UIButton
o UIView
pasará eventos táctiles que se ejecutaron en un píxel transparente.
Esta solución es algo mejor que la respuesta aceptada porque aún puede hacer clic en un UIButton
que se encuentra bajo una UIView
semitransparente, mientras que la parte no transparente de UIView
responderá a los eventos táctiles.
Como puede ver en el GIF, el botón Jirafa es un rectángulo simple, pero los eventos táctiles en áreas transparentes se transmiten al UIButton
amarillo que se UIButton
debajo.
Digamos que tenemos un controlador de vista con una sub vista. la subvista ocupa el centro de la pantalla con márgenes de 100 px en todos los lados. Luego agregamos un montón de cosas pequeñas para hacer clic dentro de esa subvista. Solo estamos utilizando la subvista para aprovechar el nuevo marco (x = 0, y = 0 dentro de la subvista es en realidad 100,100 en la vista principal).
Luego, imagine que tenemos algo detrás de la subvista, como un menú. Quiero que el usuario pueda seleccionar cualquiera de las "pequeñas cosas" en la subvista, pero si no hay nada allí, quiero que los toques pasen (ya que el fondo es claro de todos modos) a los botones detrás de ella.
¿Cómo puedo hacer esto? Parece que Toques pasa, pero los botones no funcionan.
Creé una categoría para hacer esto.
un pequeño método swizzling y la vista es dorada.
El encabezado
//UIView+PassthroughParent.h
@interface UIView (PassthroughParent)
- (BOOL) passthroughParent;
- (void) setPassthroughParent:(BOOL) passthroughParent;
@end
El archivo de implementación
#import "UIView+PassthroughParent.h"
@implementation UIView (PassthroughParent)
+ (void)load{
Swizz([UIView class], @selector(pointInside:withEvent:), @selector(passthroughPointInside:withEvent:));
}
- (BOOL)passthroughParent{
NSNumber *passthrough = [self propertyValueForKey:@"passthroughParent"];
if (passthrough) return passthrough.boolValue;
return NO;
}
- (void)setPassthroughParent:(BOOL)passthroughParent{
[self setPropertyValue:[NSNumber numberWithBool:passthroughParent] forKey:@"passthroughParent"];
}
- (BOOL)passthroughPointInside:(CGPoint)point withEvent:(UIEvent *)event{
// Allow buttons to receive press events. All other views will get ignored
if (self.passthroughParent){
if (self.alpha != 0 && !self.isHidden){
for( id foundView in self.subviews )
{
if ([foundView alpha] != 0 && ![foundView isHidden] && [foundView pointInside:[self convertPoint:point toView:foundView] withEvent:event])
return YES;
}
}
return NO;
}
else {
return [self passthroughPointInside:point withEvent:event];// Swizzled
}
}
@end
Necesitarás agregar mi Swizz.hy Swizz.m
ubicado Here
Después de eso, solo importa Importar Uiview + PassthroughParent.h en tu archivo {Project} -Prefix.pch, y cada vista tendrá esta capacidad.
cada vista tomará puntos, pero ninguno del espacio en blanco lo hará.
También recomiendo usar un fondo claro.
myView.passthroughParent = YES;
myView.backgroundColor = [UIColor clearColor];
EDITAR
Creé mi propia bolsa de propiedades, y eso no estaba incluido previamente.
Archivo de cabecera
// NSObject+PropertyBag.h
#import <Foundation/Foundation.h>
@interface NSObject (PropertyBag)
- (id) propertyValueForKey:(NSString*) key;
- (void) setPropertyValue:(id) value forKey:(NSString*) key;
@end
Archivo de Implementación
// NSObject+PropertyBag.m
#import "NSObject+PropertyBag.h"
@implementation NSObject (PropertyBag)
+ (void) load{
[self loadPropertyBag];
}
+ (void) loadPropertyBag{
@autoreleasepool {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Swizz([NSObject class], NSSelectorFromString(@"dealloc"), @selector(propertyBagDealloc));
});
}
}
__strong NSMutableDictionary *_propertyBagHolder; // Properties for every class will go in this property bag
- (id) propertyValueForKey:(NSString*) key{
return [[self propertyBag] valueForKey:key];
}
- (void) setPropertyValue:(id) value forKey:(NSString*) key{
[[self propertyBag] setValue:value forKey:key];
}
- (NSMutableDictionary*) propertyBag{
if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
NSMutableDictionary *propBag = [_propertyBagHolder valueForKey:[[NSString alloc] initWithFormat:@"%p",self]];
if (propBag == nil){
propBag = [NSMutableDictionary dictionary];
[self setPropertyBag:propBag];
}
return propBag;
}
- (void) setPropertyBag:(NSDictionary*) propertyBag{
if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
[_propertyBagHolder setValue:propertyBag forKey:[[NSString alloc] initWithFormat:@"%p",self]];
}
- (void)propertyBagDealloc{
[self setPropertyBag:nil];
[self propertyBagDealloc];//Swizzled
}
@end
Cree una vista personalizada para su contenedor y anule el mensaje pointInside: para devolver NO cuando el punto no se encuentre dentro de una vista secundaria elegible, como esta:
@interface PassthroughView : UIView
@end
@implementation PassthroughView
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
for (UIView *view in self.subviews) {
if (!view.hidden && view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event])
return YES;
}
return NO;
}
@end
El uso de esta vista como contenedor permitirá que cualquiera de sus hijos reciba toques, pero la vista en sí misma será transparente para los eventos.
Editar: Aquí está la versión Swift
class PassThroughView: UIView {
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
for subview in subviews {
if !subview.hidden && subview.userInteractionEnabled && subview.pointInside(convertPoint(point, toView: subview), withEvent: event) {
return true
}
}
return false
}
}
Swift 3:
class PassThroughView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for subview in subviews {
if !subview.isHidden && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) {
return true
}
}
return false
}
}
De acuerdo con la ''Guía de programación de aplicaciones de iPhone'':
Desactivar la entrega de eventos táctiles. De forma predeterminada, una vista recibe eventos táctiles, pero puede establecer su propiedad userInteractionEnabled en NO para desactivar la entrega de eventos. Una vista tampoco recibe eventos si está oculta o si es transparente .
Actualizado : ejemplo eliminado: vuelva a leer la pregunta ...
¿Tiene algún procesamiento de gestos en las vistas que pueden estar procesando los grifos antes de que el botón lo obtenga? ¿Funciona el botón cuando no tienes la vista transparente sobre él?
¿Algún código de muestras de código que no funciona?
La mejor solución votada no me funcionaba por completo, supongo que fue porque tenía un TabBarController en la jerarquía (como señala uno de los comentarios), de hecho estaba pasando toques a algunas partes de la interfaz de usuario, pero estaba jugando con mi La capacidad de tableView para interceptar eventos táctiles, lo que finalmente hizo fue anular hitTest en la vista que quiero ignorar toques y dejar que las subvistas de esa vista los manejen
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *view = [super hitTest:point withEvent:event];
if (view == self) {
return nil; //avoid delivering touch events to the container view (self)
}
else{
return view; //the subviews will still receive touch events
}
}
Por lo que sé, se supone que debes poder hacer esto anulando el hitTest: Lo intenté pero no pude hacerlo funcionar correctamente.
Al final, creé una serie de vistas transparentes alrededor del objeto palpable para que no lo cubrieran. Un poco de un truco para mi problema, esto funcionó bien.
Si no puede molestarse en utilizar una categoría o subclase de UIView, también puede llevar el botón hacia delante para que esté delante de la vista transparente. Esto no siempre será posible dependiendo de su aplicación, pero funcionó para mí. Siempre puedes volver a traer el botón u ocultarlo.
Sobre la base de lo que publicó John, aquí hay un ejemplo que permitirá que los eventos táctiles pasen por todas las subvistas de una vista, excepto por los botones:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// Allow buttons to receive press events. All other views will get ignored
for( id foundView in self.subviews )
{
if( [foundView isKindOfClass:[UIButton class]] )
{
UIButton *foundButton = foundView;
if( foundButton.isEnabled && !foundButton.hidden && [foundButton pointInside:[self convertPoint:point toView:foundButton] withEvent:event] )
return YES;
}
}
return NO;
}
Tomando los consejos de las otras respuestas y leyendo la documentación de Apple, creé esta biblioteca simple para resolver su problema:
https://github.com/natrosoft/NATouchThroughView
Hace que sea más fácil dibujar vistas en Interface Builder que deberían pasar a través de una vista subyacente.
Creo que el método Swizzling es excesivo y muy peligroso de hacer en el código de producción porque estás jugando directamente con la implementación de base de Apple y haciendo un cambio en toda la aplicación que podría causar consecuencias no deseadas.
Hay un proyecto de demostración y es de esperar que el README haga un buen trabajo explicando qué hacer. Para abordar el OP, cambiaría el UIView claro que contiene los botones a la clase NATouchThroughView en el Interface Builder. Luego encuentre la UIView clara que se superpone al menú que desea que se pueda tocar. Cambie esa UIView a la clase NARootTouchThroughView en Interface Builder. Incluso puede ser la UIView raíz de su controlador de vista si desea que esos toques pasen al controlador de vista subyacente. Mira el proyecto de demostración para ver cómo funciona. Es realmente bastante simple, seguro y no invasivo
Yo también uso
myView.userInteractionEnabled = NO;
No hay necesidad de subclase. Funciona bien.
El reenvío de eventos es una técnica utilizada por algunas aplicaciones. Reenvíe eventos táctiles invocando los métodos de manejo de eventos de otro objeto respondedor. Aunque esta puede ser una técnica efectiva, debe usarla con precaución. Las clases del marco UIKit no están diseñadas para recibir toques que no están vinculados a ellas ... Si desea enviar toques condicionalmente a otros respondedores en su aplicación, todos estos respondedores deberían ser instancias de sus propias subclases de UIView.
Mejores prácticas de manzanas :
No envíe eventos explícitamente a la cadena de respuesta (a través de nextResponder); en su lugar, invoque la implementación de la superclase y deje que el UIKit maneje el recorrido de la cadena de respuesta.
en cambio, puedes anular:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
en su subclase UIView y devuelva NO si desea que ese toque se envíe a la cadena de respuesta (es decir, a las vistas detrás de su vista sin nada en ella).
Swift 3
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for subview in subviews {
if subview.frame.contains(point) {
return true
}
}
return false
}