UIButton dentro de uma exibição que possui um UITapGestureRecognizer

184

Eu tenho vista com um UITapGestureRecognizer. Então, quando toco na visualização, outra visualização aparece acima dessa visualização. Essa nova visão possui três botões. Quando agora pressiono um desses botões, não recebo a ação dos botões, apenas a ação do gesto de tocar. Portanto, não posso mais usar esses botões. O que posso fazer para obter os eventos com esses botões? O estranho é que os botões ainda são destacados.

Não posso simplesmente remover o UITapGestureRecognizer depois que recebi o toque. Porque com ele a nova visão também pode ser removida. Significa que quero um comportamento como os controles de vídeo em tela cheia .

V1ru8
fonte

Respostas:

256

Você pode definir seu controlador ou visualização (o que criar o reconhecedor de gestos) como o representante do UITapGestureRecognizer. Em seguida, no delegado você pode implementar -gestureRecognizer:shouldReceiveTouch:. Na sua implementação, você pode testar se o toque pertence à sua nova subvisão e, se pertencer, instruir o reconhecedor de gestos a ignorá-lo. Algo como o seguinte:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    // test if our control subview is on-screen
    if (self.controlSubview.superview != nil) {
        if ([touch.view isDescendantOfView:self.controlSubview]) {
            // we touched our control surface
            return NO; // ignore the touch
        }
    }
    return YES; // handle the touch
}
Lily Ballard
fonte
No meu arquivo de cabeçalho, tive minha visão implementar UIGestoreRegognizerDelegate, e no meu .mi adicionei seu código acima. A torneira nunca entra nesse método, vai direto para o meu manipulador. Alguma ideia?
precisa saber é o seguinte
1
@kmehta, você provavelmente esqueceu de definir a propriedade de delegado UIGestureRecognizer.
Até
Essa resposta pode ser generalizada para lidar com todos os casos em que há uma ação do IBA envolvida?
Martin Wickman
O @Martin IBAction é compilado para voidque não deixe nenhuma informação em tempo de execução para uso na detecção. Mas o que você pode fazer é subir na hierarquia da exibição, testando UIControle retornando NOse encontrar algum controle.
Lily Ballard
thx por isso, isso me ajuda. se o botão estiver em um UIImageView, verifique se userInteractionEnabled do imageView está definido como YES.
James075
156

Como acompanhamento da resposta de Casey à resposta de Kevin Ballard:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
        if ([touch.view isKindOfClass:[UIControl class]]) {
            // we touched a button, slider, or other UIControl
            return NO; // ignore the touch
        }
    return YES; // handle the touch
}

Isso basicamente faz com que todos os tipos de controles de entrada do usuário, como botões, controles deslizantes, etc. funcionem

cdasher
fonte
1
Essa é uma solução flexível que pode ser acompanhada setCancelsTouchesInView = NOpara não acionar o gesto de toque na interação com os controles. No entanto, você pode escrever melhor:return ![touch.view isKindOfClass:[UIControl class]];
griga13 22/03
! Solução rápida 4+: return (touch.view é UIControl)
nteissler
103

Encontre esta resposta aqui: link

Você também pode usar

tapRecognizer.cancelsTouchesInView = NO;

O que impede que o reconhecedor de torneiras seja o único a capturar todas as torneiras

UPDATE - Michael mencionou o link para a documentação que descreve esta propriedade: cancelsTouchesInView

ejazz
fonte
8
Esta deve ser a resposta aceita IMO. Isso permite que cada subvisão lide com a torneira por si própria e evita que a visualização que possui um reconhecedor de tabulação anexada a ele 'faça o highjacking' da torneira. Não há necessidade de implementar um delegado, se tudo o que você quer é tornar a visualização clicável (para renunciar ao socorrista / ocultar o teclado em campos de texto etc.) .. incrível!
EeKay
4
Definitivamente, essa deve ser a resposta aceita. Essa resposta me levou a ler a documentação da Apple corretamente, o que deixa claro que o reconhecedor de gestos impedirá que as subviews obtenham os eventos reconhecidos, a menos que você faça isso.
Michael van der Westhuizen
1
Isso costumava funcionar, mas agora não está funcionando para mim .. talvez porque eu tenha um scrollView abaixo do botão? Eu tive que implementar o delegado como nas respostas acima.
Xissburg
7
Depois de experimentar um pouco, notei que isso só funcionará se a exibição que contém o reconhecedor de gestos for um irmão do UIControl, e não será se a exibição for o pai do UIControl.
Xissburg
6
Realmente não funciona como pretendido ... SIM, é impedir que o reconhecedor de toque toque no evento no UIControl. Mas ainda é executada a ação do reconhecedor, que é executada 2 ao mesmo tempo.
Tek Yin #
71

Como acompanhamento da resposta de Kevin Ballard, tive o mesmo problema e acabei usando este código:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if ([touch.view isKindOfClass:[UIButton class]]){
        return NO;
    }
    return YES;
}

Ele tem o mesmo efeito, mas isso funcionará em qualquer UIButton em qualquer profundidade de exibição (meu UIButton tinha várias visualizações de profundidade e o delegado do UIGestureRecognizer não tinha uma referência a ele.)

Casey
fonte
6
Não quero ser um idiota aqui, mas é realmente SIM e NÃO. Por favor, use as convenções de cacau. TRUE / FALSE / NULL é para a camada CoreFoundation.
steipete
pelo que li, SIM e NÃO foram realmente criados para facilitar a leitura. legibilidade é subjetiva. decorre das convenções de nomenclatura de métodos e propriedades com as quais nem todos estão preocupados demais. se você deseja uma adesão estrita à convenção de nomenclatura, sim, use SIM e NÃO para qualquer NSWhatever. no entanto, direi que, se você pesquisar desenvolvedores, obterá opiniões contraditórias sobre qual delas é mais legível [button setHidden: YES]; -ou- [botão setHidden: TRUE];
Pimp Juice McJones
1
Eu acho que o que estou tentando dizer é que, se a premissa das convenções de nomenclatura é legível, acho difícil negar que seja subjetiva em alguns casos. se você é purista, não entenderá essa afirmação.
Pimp Juice McJones
2
Pode parecer subjetivo, mas depois de um tempo de programação do cacau você realmente entra no fluxo de código mais semanticamente preciso ... o "VERDADEIRO" parece realmente errado agora depois de anos de Objective-C, porque não corresponde muito a "oculto" bem.
Kendall Helmstetter Gelner
Desde quando você pode / passa um gesto de toque para um UIButton como um evento de retoque no UITouch (o último sendo o evento gerado ao tocar em um UIButton)? Parece duplicado e incorreto criar um reconhecedor de gesto de toque para um botão que possui 15 eventos de toque já associados a um gesto de toque. Eu gostaria de ver código, não palpites.
James Bush
10

No iOS 6.0 e posterior, as ações de controle padrão impedem a sobreposição do comportamento do reconhecedor de gestos. Por exemplo, a ação padrão para um botão é um único toque. Se você tiver um reconhecedor de gestos de toque único conectado à visualização principal de um botão e o usuário tocar no botão, o método de ação do botão receberá o evento de toque em vez do reconhecedor de gestos. Isso se aplica apenas ao reconhecimento de gestos que se sobrepõe à ação padrão de um controle, que inclui: .....

Do documento API da Apple

Jagie
fonte
8

Essas respostas estavam incompletas. Eu tive que ler várias postagens sobre como usar essa operação booleana.

No seu arquivo * .h, adicione este

@interface v1ViewController : UIViewController <UIGestureRecognizerDelegate>

No seu arquivo * .m, adicione este

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {

    NSLog(@"went here ...");

    if ([touch.view isKindOfClass:[UIControl class]])
    {
        // we touched a button, slider, or other UIControl
        return NO; // ignore the touch
    }
    return YES; // handle the touch
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.



    //tap gestrure
    UITapGestureRecognizer *tapGestRecog = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenTappedOnce)];
    [tapGestRecog setNumberOfTapsRequired:1];
    [self.view addGestureRecognizer:tapGestRecog];


// This line is very important. if You don't add it then your boolean operation will never get called
tapGestRecog.delegate = self;

}


-(IBAction) screenTappedOnce
{
    NSLog(@"screenTappedOnce ...");

}
Sam B
fonte
7

Encontrou outra maneira de fazer isso a partir daqui . Ele detecta o toque dentro de cada botão ou não.

(1) pointInside: withEvent: (2) locationInView:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
       shouldReceiveTouch:(UITouch *)touch {
    // Don't recognize taps in the buttons
    return (![self.button1 pointInside:[touch locationInView:self.button1] withEvent:nil] &&
            ![self.button2 pointInside:[touch locationInView:self.button2] withEvent:nil] &&
            ![self.button3 pointInside:[touch locationInView:self.button3] withEvent:nil]);
}
MindMirror
fonte
Tentei todas as outras soluções para isso, mas a abordagem -pointInside: withEvent: foi a única que funcionou para mim.
Kenny Wyland
3

Aqui está a versão Swift da resposta de Lily Ballard que funcionou para mim:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if (scrollView.superview != nil) {
        if ((touch.view?.isDescendantOfView(scrollView)) != nil) { return false }
    }
    return true
}
John Riselvato
fonte
3

Swift 5

Botão na visão geral com tapgesture

 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if let _ = touch.view as? UIButton { return false }
    return true
}

No meu caso, implementar o hitTest funcionou para mim. Eu tive a visualização da coleção com o botão

Esse método percorre a hierarquia da visualização chamando o point(inside:with:)método de cada subvisão para determinar qual subview deve receber um evento de toque. Se point(inside:with:)retornar true, a hierarquia da subvisão é percorrida da mesma forma até que a visualização mais à frente que contém o ponto especificado seja encontrada.

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard isUserInteractionEnabled else { return nil }

    guard !isHidden else { return nil }

    guard alpha >= 0.01 else { return nil }

    guard self.point(inside: point, with: event) else { return nil }

    for eachImageCell in collectionView.visibleCells {
        for eachImageButton in eachImageCell.subviews {
            if let crossButton = eachImageButton as? UIButton {
                if crossButton.point(inside: convert(point, to: crossButton), with: event) {
                    return crossButton
                }
            }
        }
    }
    return super.hitTest(point, with: event)
}
Shruthi Pal
fonte
1

Você pode impedir que o UITapGestureRecognizer cancele outros eventos (como o toque no botão) configurando o seguinte booleano:

    [tapRecognizer setCancelsTouchesInView:NO];
Himanshu Mahajan
fonte
1

Se o seu cenário for assim:

Você tem uma visão simples e alguns UIButtons, controles UITextField adicionados como sub-visões a essa visão. Agora, você deseja descartar o teclado ao tocar em qualquer outro lugar na exibição, exceto nos controles (sub-visualizações adicionadas)

Então a solução é:

Adicione o seguinte método ao seu XYZViewController.m (que tem sua visão)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.view endEditing:YES];
}
NaveenRaghuveer
fonte
Primeiro tentei com a resposta do @cdasher , mas no meu caso, mesmo ao clicar no botão, estou tocando em touch.view como minha "visualização" no ViewController, mas não no meu controle "UIButton". Alguém pode dizer por propriedade vista do toque não está retornando à exibição original em que a TAP aconteceu
NaveenRaghuveer
1
Não funcionará se você tiver uma visualização de rolagem ou alguma outra visualização que reivindique os eventos de toque.
lkraider
1

Otimizando a resposta do cdasher, você obtém

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch 
{
    return ![touch.view isKindOfClass:[UIControl class]];
}
Pontes de argila
fonte