Interação além dos limites do UIView

É possível que um UIButton (ou qualquer outro controle para esse assunto) receba events de toque quando o quadro do UIButton está fora do quadro do pai? Porque quando eu tento isso, meu UIButton não parece ser capaz de receber nenhum evento. Como eu trabalho com isso?

Sim. Você pode replace o método hitTest:withEvent: para retornar uma exibição para um conjunto maior de pontos do que a visualização contém. Veja a referência da class UIView .

Editar: Exemplo:

 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { CGFloat radius = 100.0; CGRect frame = CGRectMake(-radius, -radius, self.frame.size.width + radius, self.frame.size.height + radius); if (CGRectContainsPoint(frame, point)) { return self; } return nil; } 

Edit 2: (Após esclarecimento 🙂 Para garantir que o botão seja tratado como estando dentro dos limites do pai, você precisa replace pointInside:withEvent: no pai para include o frame do botão.

 - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { if (CGRectContainsPoint(self.view.bounds, point) || CGRectContainsPoint(button.view.frame, point)) { return YES; } return NO; } 

Observe que o código existente apenas para replace o pointInside não está correto. Como Summon explica abaixo, faça isso:

 -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { if ( CGRectContainsPoint(self.oversizeButton.frame, point) ) return YES; return [super pointInside:point withEvent:event]; } 

Note que você provavelmente faria isso com self.oversizeButton como um IBOutlet nesta subclass de UIView; então você pode simplesmente arrastar o “botão oversize” em questão para a visualização especial em questão. (Ou, se por algum motivo você estivesse fazendo muito isso em um projeto, você teria uma subclass UIButton especial, e você poderia ver sua lista de subvisualização para essas classs.) Espero que ajude.

@jnic, estou trabalhando no iOS SDK 5.0 e para fazer seu código funcionar corretamente, tive que fazer isso:

 - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { if (CGRectContainsPoint(button.frame, point)) { return YES; } return [super pointInside:point withEvent:event]; } 

A visão container no meu caso é um UIButton e todos os elementos filhos também são UIButtons que podem se mover fora dos limites do UIButton pai.

Melhor

Na exibição pai, você pode replace o método de teste de hit:

 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { CGPoint translatedPoint = [_myButton convertPoint:point fromView:self]; if (CGRectContainsPoint(_myButton.bounds, translatedPoint)) { return [_myButton hitTest:translatedPoint withEvent:event]; } return [super hitTest:point withEvent:event]; } 

Nesse caso, se o ponto estiver dentro dos limites do botão, você encaminhará a chamada para lá; se não, reverta para a implementação original.

Rápido:

Onde targetView é a exibição que você deseja receber o evento (que pode estar total ou parcialmente localizado fora do quadro de sua exibição pai).

 override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { let pointForTargetView: CGPoint = targetView.convertPoint(point, fromView: self) if CGRectContainsPoint(targetView.bounds, pointForTargetView) { return closeButton } return super.hitTest(point, withEvent: event) } 

No meu caso, eu tinha uma subclass UICollectionViewCell que continha um UIButton . clipsToBounds na célula e o botão ficou visível fora dos limites da célula. No entanto, o botão não estava recebendo events de toque. Consegui detectar os events de toque no botão usando a resposta de Jack: https://stackoverflow.com/a/30431157/3344977

Aqui está uma versão do Swift:

 override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { let translatedPoint = button.convertPoint(point, fromView: self) if (CGRectContainsPoint(button.bounds, translatedPoint)) { print("Your button was pressed") return button.hitTest(translatedPoint, withEvent: event) } return super.hitTest(point, withEvent: event) } 

Porque isso está acontecendo?

Isso ocorre porque, quando sua subvisualização está fora dos limites da sua super visão, os events de toque que realmente acontecem nessa subvisão não serão entregues a essa subvisão. No entanto, será entregue à sua super visão.

Independentemente de as visualizações serem ou não cortadas visualmente, os events de toque sempre respeitam o retângulo de limites da superview da vista de destino. Em outras palavras, os events de toque que ocorrem em uma parte de uma visão que fica fora do retângulo de seus limites de super visão não são entregues a essa visão. Ligação

O que você precisa fazer?

Quando sua super visão receber o evento de toque mencionado acima, você precisará informar ao UIKit explicitamente que minha subvisualização deve ser a pessoa a receber esse evento de toque.

E o código?

Na sua superview, implemente func hitTest(_ point: CGPoint, with event: UIEvent?)

 override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if isHidden || alpha == 0 || clipsToBounds { return nil } // convert the point into subview's coordinate system let subviewPoint = self.convert(point, to: subview) // if the converted point lies in subview's bound, tell UIKit that subview should be the one that receives this event if ! subview.isHidden && subview.bounds.contains(subviewPoint) { return subview } return nil } 

Para mim (como para outros), a resposta foi não sobrepor o método hitTest , mas sim o método pointInside . Eu tive que fazer isso em apenas dois lugares.

A Superview Essa é a visão de que, se tivesse clipsToBounds configurado como true, isso faria com que todo esse problema desaparecesse. Então aqui está o meu simples UIView no Swift 3:

 class PromiscuousView : UIView { override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return true } } 

A subvisão Sua milhagem pode variar, mas no meu caso eu também tive que replace o método pointInside para a subvisualização que foi decidida que o toque estava fora dos limites. O meu está no Objective-C, então:

 - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { WEPopoverController *controller = (id) self.delegate; if (self.takeHitsOutside) { return YES; } else { return [super pointInside:point withEvent:event]; } } 

Essa resposta (e algumas outras sobre a mesma pergunta) pode ajudar a esclarecer sua compreensão.