iOS - encaminhar todos os toques através de uma visualização

141

Tenho uma vista sobreposta a muitas outras. Estou usando apenas a overaly para detectar alguns toques na tela, mas, além disso, não quero que a exibição pare o comportamento de outras exibições abaixo, que são visualizações de rolagem, etc. Como posso encaminhar todos os toques através essa visão de sobreposição? É um subescala do UIView.

Sol
fonte
2
resolveu seu problema? Se sim, aceite a resposta para que seja mais fácil para outros encontrar a solução, pois estou enfrentando o mesmo problema.
Khushbu Desai
esta resposta funciona bem stackoverflow.com/a/4010809/4833705
Lance Samaria

Respostas:

121

Desativar a interação do usuário era tudo o que eu precisava!

Objetivo-C:

myWebView.userInteractionEnabled = NO;

Rápido:

myWebView.isUserInteractionEnabled = false
rmp251
fonte
Isso funcionou na minha situação em que eu tinha uma sub-visualização do botão. Desativei a interação na subvisão e o botão funcionou.
simple_code
2
Em Swift 4,myWebView.isUserInteractionEnabled = false
Louis 'LYRO' Dupont 02/09/19
117

Para passar toques de uma vista de sobreposição para as vistas abaixo, implemente o seguinte método no UIView:

Objetivo-C:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"Passing all touches to the next view (if any), in the view stack.");
    return NO;
}

Swift 5:

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    print("Passing all touches to the next view (if any), in the view stack.")
    return false
}
PixelCloudSt
fonte
3
Eu tenho o mesmo problema, mas o que eu quero é que, se estiver ampliando / girando / movendo a vista com um gesto, ele retorne sim e, para o resto, retorne não, como posso fazer isso?
Minakshi
@Minakshi, que é uma pergunta totalmente diferente, faça uma nova pergunta para isso.
Fattie
mas esteja ciente de que essa abordagem simples NÃO DEVE TER botões e assim por diante NA PARTE SUPERIOR da camada em questão!
Fattie 15/10/19
56

Este é um tópico antigo, mas surgiu em uma pesquisa, então pensei em adicionar meu 2c. Eu tenho um UIView de cobertura com sub-visualizações e só quero interceptar os toques que atingem uma das sub-visualizações. Por isso, modifiquei a resposta do PixelCloudSt para

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView* subview in self.subviews ) {
        if ( [subview hitTest:[self convertPoint:point toView:subview] withEvent:event] != nil ) {
            return YES;
        }
    }
    return NO;
}
Fresidue
fonte
1
Você precisa criar uma subclasse UIView personalizada (ou uma subclasse de qualquer outra subclasse UIView, como UIImageView ou qualquer outra coisa) e substituir essa função. Em essência, a subclasse pode ter uma '@interface' totalmente vazia no arquivo .h, e a função acima é todo o conteúdo da '@implementation'.
fresidue
Obrigado, é exatamente isso que eu precisava para passar toques pelo espaço vazio da minha UIView, para ser tratado pela vista por trás. +100
Danny
41

Versão aprimorada da resposta @fresidue. Você pode usar essa UIViewsubclasse como vista transparente passando toques fora de sua subvisão. Implementação no objetivo C :

@interface PassthroughView : UIView

@end

@implementation PassthroughView

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView *view in self.subviews) {
        if (!view.hidden && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
            return YES;
        }
    }
    return NO;
}

@end

.

e em Swift:

class PassthroughView: UIView {
  override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    return subviews.contains(where: {
      !$0.isHidden
      && $0.isUserInteractionEnabled
      && $0.point(inside: self.convert(point, to: $0), with: event)
    })
  }
}

DICA:

Digamos que você tenha um grande painel "suporte", talvez com uma vista de mesa por trás. Você faz o painel "titular" PassthroughView. Agora funcionará, você pode rolar a tabela "através" do "suporte".

Mas!

  1. Na parte superior do painel "suporte", existem alguns rótulos ou ícones. Não se esqueça, é claro que essas devem ser simplesmente marcadas como interação do usuário ativada como OFF!

  2. Na parte superior do painel "suporte", você tem alguns botões. Não se esqueça, é claro que essas devem simplesmente ser marcadas como interação do usuário ativada!

  3. Observe que, de maneira um tanto confusa, o próprio "suporte" - a visualização em que você usa PassthroughView- deve estar marcado como Ativado ! Isso é ON !! (Caso contrário, o código no PassthroughView simplesmente nunca será chamado.)

Timur Bernikovich
fonte
1
Eu usei a solução dada para o Swift. Ele está funcionando no ios 11, mas é sempre travado no iOS 10. Mostrando acesso ruim. Qualquer ideia?
Soumen
Você salvou meus dias, funcionou para passar toques nos UIViews abaixo.
AaoIi
1
Não se esqueça de verificar também:$0.isUserInteractionEnabled
Sean Thielen
7

Eu tive um problema semelhante com um UIStackView (mas poderia ser qualquer outro modo de exibição). Minha configuração foi a seguinte: Controlador de exibição com containerView e StackView

É um caso clássico em que eu tenho um recipiente que precisava ser colocado em segundo plano, com botões ao lado. Para fins de layout, incluí os botões em um UIStackView, mas agora a parte do meio (vazia) do stackView intercepta toques :-(

O que fiz foi criar uma subclasse de UIStackView com uma propriedade que define o subView que deve ser tocável. Agora, qualquer toque nos botões laterais (incluído na matriz * viewsWithActiveTouch *) será dado aos botões, enquanto qualquer toque no stackview em qualquer outro lugar que não seja esse ponto de vista não será interceptado e, portanto, transmitido para o que estiver abaixo da pilha Visão.

/** Subclass of UIStackView that does not accept touches, except for specific subviews given in the viewsWithActiveTouch array */
class NoTouchStackView: UIStackView {

  var viewsWithActiveTouch: [UIView]?

  override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

    if let activeViews = viewsWithActiveTouch {
        for view in activeViews {
           if CGRectContainsPoint(view.frame, point) {
                return view

           }
        }
     }
     return nil
   }
}
Frédéric Adda
fonte
6

Se a visualização para a qual você deseja encaminhar os toques não for uma subview / superview, você poderá configurar uma propriedade customizada em sua subclasse UIView da seguinte forma:

@interface SomeViewSubclass : UIView {

    id forwardableTouchee;

}
@property (retain) id forwardableTouchee;

Certifique-se de sintetizá-lo no seu .m:

@synthesize forwardableTouchee;

E inclua o seguinte em qualquer um dos seus métodos UIResponder, como:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    [self.forwardableTouchee touchesBegan:touches withEvent:event];

}

Sempre que você instancia sua UIView, defina a propriedade forwardableTouchee para qualquer visualização que você deseja que os eventos sejam encaminhados:

    SomeViewSubclass *view = [[[SomeViewSubclass alloc] initWithFrame:someRect] autorelease];
    view.forwardableTouchee = someOtherView;
Chris Ladd
fonte
4

No Swift 5

class ThroughView: UIView {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        guard let slideView = subviews.first else {
            return false
        }

        return slideView.hitTest(convert(point, to: slideView), with: event) != nil
    }
}
onmyway133
fonte
4

Eu precisava passar detalhes através de um UIStackView. Uma UIView dentro era transparente, mas o UIStackView consumia todos os toques. Isso funcionou para mim:

class PassThrouStackView: UIStackView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let view = super.hitTest(point, with: event)
        if view == self {
            return nil
        }
        return view
    }
}

Todas as Subviews arranjadas ainda recebem toques, mas os toques no próprio UIStackView passaram pela visualização abaixo (para mim, um mapView).

gorkem
fonte
2

Parece que mesmo com muitas respostas aqui, não há ninguém tão rápido que eu precisei. Então, peguei a resposta do @fresidue aqui e a converti para rápida, pois é o que agora a maioria dos desenvolvedores deseja usar aqui.

Ele resolveu o meu problema, onde eu tenho uma barra de ferramentas transparente com o botão, mas quero que a barra de ferramentas fique invisível para o usuário e os eventos de toque devem passar.

isUserInteractionEnabled = false, como indicado anteriormente, não é uma opção baseada nos meus testes.

 override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for subview in subviews {
        if subview.hitTest(convert(point, to: subview), with: event) != nil {
            return true
        }
    }
    return false
}
Renetik
fonte
1

Eu tinha alguns rótulos dentro do StackView e não tive muito sucesso com as soluções acima. Em vez disso, resolvi meu problema usando o código abaixo:

    let item = ResponsiveLabel()

    // Configure label

    stackView.addArrangedSubview(item)

Subclasse UIStackView:

class PassThrouStackView:UIStackView{
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        for subview in self.arrangedSubviews {
            let convertedPoint = convert(point, to: subview)
            let labelPoint = subview.point(inside: convertedPoint, with: event)
            if (labelPoint){
                return subview
            }

        }
        return nil
    }

}

Então você pode fazer algo como:

class ResponsiveLabel:UILabel{
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Respond to touch
    }
}
Amir.n3t
fonte
0

Tente algo assim ...

for (UIView *view in subviews)
  [view touchesBegan:touches withEvent:event];

O código acima, no seu método touchesBegan, por exemplo, passaria os toques para todas as subvisões de exibição.

Jordânia
fonte
6
O problema com essa abordagem do AFAIK é que a tentativa de encaminhar toques para as classes da estrutura do UIKit geralmente não terá efeito. De acordo com a documentação da Apple, as classes de estrutura do UIKit não são projetadas para receber toques que tenham a propriedade view definida para alguma outra view. Portanto, essa abordagem funciona principalmente para subclasses personalizadas do UIView.
maciejs
0

A situação que eu estava tentando fazer era criar um painel de controle usando controles dentro do UIStackView aninhado. Alguns dos controles tinham outros UITextField com UIButton. Além disso, havia rótulos para identificar os controles. O que eu queria fazer era colocar um grande botão "invisível" atrás do painel de controle, para que, se um usuário tocasse em uma área fora de um botão ou campo de texto, ele pudesse capturá-lo e tomar uma ação - principalmente descartar qualquer teclado se um texto O campo estava ativo (resignFirstResponder). No entanto, tocar em uma etiqueta ou outra área em branco no painel de controle não passaria nada. As discussões acima foram úteis para apresentar minha resposta abaixo.

Basicamente, classifiquei o UIStackView e substitui a rotina "point (inside: with) para procurar o tipo de controle que precisava do toque e" ignorar "coisas como etiquetas que eu queria ignorar. Ele também verifica se há dentro do UIStackView, para que as coisas possam voltar à estrutura do painel de controle.

O código é talvez um pouco mais detalhado do que deveria ser. Mas foi útil na depuração e, esperançosamente, fornece mais clareza no que a rotina está fazendo. Apenas certifique-se de que no Interface Builder altere a classe do UIStackView para PassThruStack.

class PassThruStack: UIStackView {

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {

    for view in self.subviews {
        if !view.isHidden {
            let isStack = view is UIStackView
            let isButton = view is UIButton
            let isText = view is UITextField
            if isStack || isButton || isText {
                let pointInside = view.point(inside: self.convert(point, to: view), with: event)
                if pointInside {
                    return true
                }
            }
        }
    }
    return false
}

}

anorskdev
fonte
-1

Conforme sugerido pelo @PixelCloudStv, se você deseja tocar o assunto de uma visualização para outra, mas com algum controle adicional sobre esse processo - subclasse UIView

//cabeçalho

@interface TouchView : UIView

@property (assign, nonatomic) CGRect activeRect;

@end

//implementação

#import "TouchView.h"

@implementation TouchView

#pragma mark - Ovverride

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    BOOL moveTouch = YES;
    if (CGRectContainsPoint(self.activeRect, point)) {
        moveTouch = NO;
    }
    return moveTouch;
}

@end

Depois, no interfaceBuilder, basta definir a classe de exibição como TouchView e definir ret ativo com seu ret. Também você pode mudar e implementar outra lógica.

gbk
fonte