Como posso clicar em um botão atrás de um UIView transparente?

Digamos que temos um controlador de visualização com uma subvisualização. a subguia ocupa o centro da canvas com margens de 100 px em todos os lados. Em seguida, adicionamos um monte de pequenas coisas para clicar dentro dessa subvisão. Estamos usando apenas a subvisão para tirar proveito do novo quadro (x = 0, y = 0 dentro da subvisualização é, na verdade, 100.100 na visualização pai).

Então, imagine que temos algo por trás da subvisualização, como um menu. Eu quero que o usuário seja capaz de selecionar qualquer uma das “pequenas coisas” na subvisão, mas se não houver nada lá, eu quero que os toques passem por ela (já que o fundo é claro de qualquer maneira) para os botões por trás dela.

Como posso fazer isso? Parece que toques de Vegetarianismo passam, mas os botões não funcionam.

Crie uma visualização personalizada para seu contêiner e substitua a mensagem pointInside: para retornar NÃO quando o ponto não estiver em uma vista filha qualificada, 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 

Usar essa exibição como um contêiner permitirá que qualquer um de seus filhos receba toques, mas a exibição em si será transparente para os events.

Edit: Aqui está a versão 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 } } 

Eu também uso

 myView.userInteractionEnabled = NO; 

Não há necessidade de subclass. Funciona bem.

Da Apple:

O encaminhamento de events é uma técnica usada por alguns aplicativos. Você encaminha os events de toque invocando os methods de tratamento de events de outro object respondedor. Embora isso possa ser uma técnica eficaz, você deve usá-lo com caucanvas. As classs da estrutura do UIKit não são projetadas para receber toques que não estão vinculados a elas … Se você quiser encaminhar condicionalmente os toques para outros respondentes em sua aplicação, todos esses respondedores devem ser instâncias de suas próprias subclasss do UIView.

Melhor Prática de Maçãs :

Não envie events explicitamente para a cadeia de respostas (via nextResponder); em vez disso, invoque a implementação da superclass e deixe o UIKit manipular o percurso da cadeia de respostas.

em vez disso, você pode replace:

 -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 

em sua subclass UIView e retorne NÃO se você quiser que o toque seja enviado para a cadeia de respostas (ou seja, para visões por trás de sua visualização sem nada).

Ultimamente eu escrevi uma aula que me ajudará com isso. Usá-lo como uma class personalizada para um UIButton ou UIView passará por events de toque que foram executados em um pixel transparente.

Essa solução é um pouco melhor do que a resposta aceita, porque você ainda pode clicar em um UIButton que está sob um UIView semitransparente, enquanto a parte não transparente do UIView ainda responderá aos events de toque.

GIF

Como você pode ver no GIF, o botão Giraffe é um retângulo simples, mas os events de toque em áreas transparentes são passados ​​para o UIButton amarelo embaixo.

Link para a turma

Uma maneira muito mais simples é “Un-Check” User Interaction Enabled no construtor de interface. “se você estiver usando um storyboard”

insira a descrição da imagem aqui

Com base no que John postou, aqui está um exemplo que permitirá que os events de toque passem por todas as subvisões de uma visualização, exceto pelos botões:

 -(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; } 

Eu criei uma categoria para fazer isso.

um pouco de mistura e a vista é dourada.

O header

 //UIView+PassthroughParent.h @interface UIView (PassthroughParent) - (BOOL) passthroughParent; - (void) setPassthroughParent:(BOOL) passthroughParent; @end 

O arquivo de implementação

 #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 

Você precisará adicionar meu Swizz.he Swizz.m

localizado aqui

Depois disso, basta importar o UIView + PassthroughParent.h no arquivo {Project} -Prefix.pch e todas as exibições terão essa capacidade.

toda visão levará pontos, mas nenhum do espaço em branco irá.

Eu também recomendo usar um fundo claro.

 myView.passthroughParent = YES; myView.backgroundColor = [UIColor clearColor]; 

EDITAR

Eu criei minha própria bolsa de propriedades, e isso não foi incluído anteriormente.

Arquivo de header

 // NSObject+PropertyBag.h #import  @interface NSObject (PropertyBag) - (id) propertyValueForKey:(NSString*) key; - (void) setPropertyValue:(id) value forKey:(NSString*) key; @end 

Arquivo de Implementação

 // 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 

De acordo com o ‘iPhone Application Programming Guide’:

Desligar a entrega de events de toque. Por padrão, uma exibição recebe events de toque, mas você pode definir sua propriedade userInteractionEnabled como NO para desativar a entrega de events. Uma visão também não recebe events se estiver oculta ou transparente .

http://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/EventHandling/EventHandling.html

Atualizado : exemplo removido – releia a pergunta …

Você tem algum processamento de gestos nas visualizações que podem estar processando os toques antes que o botão os obtenha? O botão funciona quando você não tem a visão transparente sobre ele?

Algum exemplo de código de código não funcional?

Tomando dicas das outras respostas e lendo a documentação da Apple, criei esta biblioteca simples para resolver o seu problema:
https://github.com/natrosoft/NATouchThroughView
Isso facilita a criação de exibições no Interface Builder que devem passar por uma visão subjacente.

Eu acho que o método swizzling é um exagero e muito perigoso de se fazer no código de produção, porque você está mexendo diretamente com a implementação da base da Apple e fazendo uma mudança em toda a aplicação que pode causar conseqüências não intencionais.

Existe um projeto de demonstração e esperamos que o README faça um bom trabalho explicando o que fazer. Para endereçar o OP, você mudaria o UIView claro que contém os botões para classificar o NATouchThroughView no Interface Builder. Em seguida, localize o UIView claro que sobrepõe o menu que você quer que seja ativado por um toque. Mude esse UIView para a class NARootTouchThroughView no Interface Builder. Pode até ser a UIView raiz de seu controlador de visualização se você pretende que esses toques passem para o controlador de visualização subjacente. Confira o projeto de demonstração para ver como funciona. É realmente muito simples, seguro e não invasivo

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 } 

Tanto quanto eu sei, você deve ser capaz de fazer isso substituindo o método hitTest:. Eu tentei, mas não consegui fazê-lo funcionar corretamente.

No final, criei uma série de visões transparentes em torno do object palpável, para que elas não fossem cobertas. Pouco de um hack para o meu problema isso funcionou bem.

Se você não pode se preocupar em usar uma categoria ou subclass UIView, você também pode simplesmente trazer o botão para frente para que fique na frente da visão transparente. Isso nem sempre será possível dependendo do seu aplicativo, mas funcionou para mim. Você sempre pode trazer o botão de volta ou ocultá-lo.

A solução mais votada não foi totalmente funcional para mim, eu acho que foi porque eu tinha um TabBarController na hierarquia (como um dos comentários aponta) foi de fato passando toques para algumas partes da interface do usuário, mas estava mexendo com o meu capacidade do tableView para interceptar events de toque, o que finalmente fez foi replace hitTest na exibição que eu quero ignorar toques e deixar as subviews dessa visão lidar com eles

 - (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 } }