UIButton all'interno di una vista che ha un UITapGestureRecognizer


184

Ho vista con a UITapGestureRecognizer. Quindi quando tocco sulla vista appare un'altra vista sopra questa vista. Questa nuova vista ha tre pulsanti. Quando ora premo uno di questi pulsanti non ottengo l'azione dei pulsanti, ottengo solo l'azione del gesto del tocco. Quindi non sono più in grado di usare questi pulsanti. Cosa posso fare per far arrivare gli eventi a questi pulsanti? La cosa strana è che i pulsanti vengono ancora evidenziati.

Non riesco a rimuovere UITapGestureRecognizer solo dopo aver ricevuto il tocco. Perché con esso è anche possibile rimuovere la nuova vista. Significa che voglio un comportamento come i controlli vide a schermo intero .

Risposte:


256

È possibile impostare il controller o la visualizzazione (a seconda di quale sia il riconoscimento dei gesti) come delegato di UITapGestureRecognizer. Quindi nel delegato è possibile implementare -gestureRecognizer:shouldReceiveTouch:. Nella tua implementazione puoi verificare se il tocco appartiene alla tua nuova sottoview e, in tal caso, istruire il riconoscimento dei gesti a ignorarlo. Qualcosa di simile al seguente:

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

Nel mio file di intestazione, ho avuto la mia vista implementare UIGestoreRegognizerDelegate e nel mio .mi ho aggiunto il codice sopra. Il rubinetto non entra mai in questo metodo, va direttamente al mio gestore. Qualche idea?
kmehta,

1
@kmehta molto probabilmente hai dimenticato di impostare la proprietà del delegato UIGestureRecognizer.
Fino

Questa risposta può essere generalizzata per gestire tutti i casi in cui è coinvolta un'IBAction?
Martin Wickman,

@Martin IBAction si compila in voidmodo da non lasciare alcuna informazione in fase di esecuzione da utilizzare per il rilevamento. Ma quello che puoi fare è risalire la gerarchia della vista, testare UIControle tornare NOse trovi qualche controllo.
Lily Ballard,

grazie per questo, mi aiuta. se il tuo pulsante è in UIImageView, assicurati che userInteractionEnabled di imageView sia impostato su SÌ.
james075

156

Come seguito alla risposta di Casey alla risposta di 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
}

Questo in pratica fa funzionare tutti i tipi di controlli di input dell'utente come pulsanti, cursori, ecc


1
Questa è una soluzione flessibile che può essere accompagnata setCancelsTouchesInView = NOper non attivare il gesto del tocco sull'interazione con i controlli. Comunque puoi scriverlo meglio:return ![touch.view isKindOfClass:[UIControl class]];
griga13

Soluzione Swift 4+: return! (Touch.view is UIControl)
nteissler

103

Ho trovato questa risposta qui: link

Puoi anche usare

tapRecognizer.cancelsTouchesInView = NO;

Ciò impedisce al riconoscitore di tocchi di essere l'unico a catturare tutti i tocchi

AGGIORNAMENTO - Michael ha menzionato il collegamento alla documentazione che descrive questa proprietà: cancelsTouchesInView


8
Questa dovrebbe essere la risposta accettata IMO. Ciò consente a ogni sottoview di gestire il rubinetto da solo e impedisce alla vista a cui è associato un riconoscimento di tabulazioni di "highjacking" il tocco. Non è necessario implementare un delegato se tutto ciò che si desidera fare è fare clic su una vista (per dimettersi dal primo risponditore / nascondere la tastiera nei campi di testo, ecc.) Fantastico!
EeKay,

4
Questa dovrebbe essere sicuramente la risposta accettata. Questa risposta mi ha portato a leggere correttamente la documentazione di Apple , il che chiarisce che il riconoscimento dei gesti impedirà alle sottoview di ottenere gli eventi riconosciuti a meno che tu non lo faccia.
Michael van der Westhuizen,

1
Questo funzionava ma ora non funziona per me ... forse perché ho un scrollView sotto il pulsante? Ho dovuto implementare il delegato come nelle risposte sopra.
xissburg,

7
Dopo aver sperimentato un po ', ho notato che questo funzionerà solo se la vista che contiene il riconoscimento dei gesti è un fratello di UIControl, e non lo farà se la vista è il genitore di UIControl.
xissburg,

6
Non funziona davvero come previsto ... SÌ, impedisce il riconoscimento dei tocchi per mangiare l'evento su UIControl. Ma è ancora in esecuzione l'azione di riconoscimento, che viene eseguita contemporaneamente all'azione 2.
Tek Yin

71

Come seguito alla risposta di Kevin Ballard, ho avuto lo stesso problema e ho finito con questo codice:

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

Ha lo stesso effetto, ma funzionerà su qualsiasi UIButton a qualsiasi profondità di vista (il mio UIButton era di diverse viste in profondità e il delegato di UIGestureRecognizer non ne aveva alcun riferimento).


6
Non voglio essere un cazzone qui, ma è davvero SÌ e NO. Si prega di utilizzare le convenzioni sul cacao. TRUE / FALSE / NULL è per CoreFoundation-Layer.
steipete,

da quello che ho letto, SÌ e NO sono stati effettivamente creati per essere leggibili. la leggibilità è soggettiva. deriva dalle convenzioni sui nomi di metodi e proprietà di cui non tutti sono eccessivamente preoccupati. se si desidera una stretta aderenza alla convenzione di denominazione, sì, utilizzare SÌ e NO per qualsiasi NSWhatever. tuttavia, dirò che se sondate gli sviluppatori otterrete opinioni contrastanti su quale di questi sia più leggibile [button setHidden: YES]; -oppure- [pulsante setHidden: TRUE];
Pimp Juice McJones,

1
suppongo che ciò che sto cercando di dire è che se la premessa delle convenzioni di denominazione sono leggibilità, penso che sia difficile negare che in alcuni casi è soggettivo. se sei un purista, allora non capirai questa affermazione.
Pimp Juice McJones,

2
Può sembrare soggettivo ma dopo un po 'di programmazione Cocoa si entra davvero nel flusso di codice semanticamente più accurato ... il "VERO" sembra davvero sbagliato ora dopo anni di Objective-C, perché non corrisponde molto "nascosto" bene.
Kendall Helmstetter Gelner,

Da quando è possibile / si passa un gesto di tocco a un UIButton come Ritocco all'interno di UITouch Event (quest'ultimo è l'evento generato toccando un UIButton)? Sembra duplicativo e errato creare un riconoscimento gesti tocco per un pulsante che ha 15 eventi tocco già associati a un gesto tocco. Mi piacerebbe vedere il codice, non indovinare.
James Bush,

10

In iOS 6.0 e versioni successive, le azioni di controllo predefinite impediscono il comportamento del riconoscimento gesti sovrapposto. Ad esempio, l'azione predefinita per un pulsante è un singolo tocco. Se si dispone di un riconoscimento dei gesti con tocco singolo collegato alla vista padre di un pulsante e l'utente tocca il pulsante, il metodo di azione del pulsante riceve l'evento touch anziché il riconoscimento gesti. Questo vale solo per il riconoscimento dei gesti che si sovrappone all'azione predefinita per un controllo, che include: .....

Dal documento API di Apple


8

Queste risposte erano incomplete. Ho dovuto leggere più post su come utilizzare questa operazione booleana.

Nel tuo file * .h aggiungi questo

@interface v1ViewController : UIViewController <UIGestureRecognizerDelegate>

Nel tuo file * .m aggiungi questo

- (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 ...");

}

7

Ho trovato un altro modo per farlo da qui . Rileva il tocco all'interno o all'esterno di ciascun pulsante.

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

Ho provato tutte le altre soluzioni per questo, ma l'approccio -pointInside: withEvent: era l'unico che ha funzionato per me.
Kenny Wyland,

3

Ecco la versione rapida della risposta di Lily Ballard che ha funzionato per me:

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

3

Swift 5

Pulsante su superview con tapgesture

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

Nel mio caso l'implementazione di hitTest ha funzionato per me. Ho avuto la vista della raccolta con il pulsante

Questo metodo attraversa la gerarchia della vista chiamando il point(inside:with:)metodo di ogni sottoview per determinare quale sottoview dovrebbe ricevere un evento touch. Se point(inside:with:)restituisce true, la gerarchia della vista secondaria viene attraversata in modo simile fino a quando non viene trovata la vista più anteriore contenente il punto specificato.

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

1

Puoi impedire a UITapGestureRecognizer di annullare altri eventi (come il tocco sul pulsante) impostando il seguente valore booleano:

    [tapRecognizer setCancelsTouchesInView:NO];

1

Se il tuo scenario è così:

Hai una vista semplice e alcuni UIButtons, i controlli UITextField aggiunti come sottoview a quella vista. Ora vuoi chiudere la tastiera quando tocchi in qualsiasi altro punto della vista tranne che sui controlli (visualizzazioni secondarie che hai aggiunto)

Quindi la soluzione è:

Aggiungi il seguente metodo al tuo XYZViewController.m (che ha la tua vista)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.view endEditing:YES];
}

Per prima cosa ho provato con la risposta di @cdasher ma nel mio caso, anche facendo clic sul pulsante, ottengo touch.view come la mia "vista" in ViewController ma non il mio controllo "UIButton". Qualcuno può dire perché la proprietà della vista del tocco non sta restituendo la vista originale in cui si è verificato il tocco
NaveenRaghuveer

1
Non funzionerà se si dispone di una vista di scorrimento o di un'altra vista che rivendica gli eventi touch.
Lkraider,

1

Ottimizzando la risposta di cdasher, ottieni

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch 
{
    return ![touch.view isKindOfClass:[UIControl class]];
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.