UITapGestureRecognizer tocca self.view ma ignora le visualizzazioni secondarie


95

Ho bisogno di implementare una funzionalità che richiamerà del codice quando tocco due volte su self.view (visualizzazione di UIViewCotroller). Ma il problema è che ho altri oggetti dell'interfaccia utente in questa vista e non voglio allegare alcun oggetto di riconoscimento a tutti loro. Ho trovato questo metodo di seguito come eseguire i gesti sulla mia vista e so come funziona. In questo momento sono di fronte a un handicap in che modo scegliere per creare questo riconoscitore ignorando la sottoview. Qualche idea? Grazie.

UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
[doubleTap setNumberOfTapsRequired:2];
[self.view addGestureRecognizer:doubleTap];

1
Non ne sono sicuro, ma hai provato a impostare cancelsTouchesInView su NO sul riconoscimento? quindi [doubleTap setCancelsTouchesInView: NO];
JDx

Risposte:


144

È necessario adottare il UIGestureRecognizerDelegateprotocollo all'interno selfdell'oggetto e chiamare il metodo seguente per controllare la vista. All'interno di questo metodo, controlla la tua vista touch.viewe restituisci il valore bool appropriato (Sì / No). Qualcosa come questo:

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

Modifica: per favore, controlla anche la risposta di @ Ian!

Swift 5

// MARK: UIGestureRecognizerDelegate methods, You need to set the delegate of the recognizer
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
     if touch.view?.isDescendant(of: tableView) == true {
        return false
     }
     return true
}

un'altra domanda ho pochi pulsanti nella mia vista che devono funzionare. come posso impostare una priorità per loro?
Matrosov Alexander

se i tuoi pulsanti hanno i loro riconoscitori di gesti (probabilmente), scavare nei loro interni e afferrarli e leggere i documenti, -[UIGestureRecognizer requireGestureRecognizerToFail:]ma potrebbe funzionare senza modificarli
bshirley

lo cercavo da ore! Grazie! ;)
MasterRazer

Se il iftest fallisce, l'implementazione non riesce a restituire un BOOL; per uno stile corretto, restituisci YES dopo un blocco if (utilizzando { }) o in un elseramo. Grazie, però, mi hai risparmiato un po 'di lettura.
Rapina

2
Una vista è un discendente di se stessa, quindi non funziona ... (l'approccio generale è ok, vedi l'altra risposta)
Ixx

107

Un altro approccio consiste nel confrontare solo se la vista del tocco è la vista dei gesti, perché i discendenti non supereranno la condizione. Una bella e semplice battuta:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view == gestureRecognizer.view
}

2
Questo funziona meglio per me rispetto alla risposta accettata. Molto più conciso.
Suman Roy

1
@Ian questo metodo non viene richiamato quando si tocca la vista.
Praburaj

1
Ottima risposta concisa!
scurioni

La risposta accettata non ha nemmeno funzionato, ma per me ha funzionato perfettamente.
Shalin Shah

@Prabu probabilmente perché il delegato del riconoscimento dei gesti deve essere impostato prima
Kubba

23

E per la variante Swift:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if touch.view.isDescendantOfView(yourSubView){
        return false
    }
    return true
}

Buono a sapersi, isDescendantOfViewrestituisce un Booleanvalore che indica se il destinatario è una vista secondaria di una data vista o identica a quella vista.


1
Non dimenticare di impostare UIGestureRecognizerDelegate nella tua classe!
Fox5150

4
Invece di fare quella dichiarazione if, non puoi semplicemente restituire questo? return! touch.view.isDescendant (of: gestureRecognizer.view) Inoltre, nuova sintassi in swift 3 ^^
EdwardSanchez

12

Con Swift 5 e iOS 12, UIGestureRecognizerDelegateha un metodo chiamato gestureRecognizer(_:shouldReceive:). gestureRecognizer(_:shouldReceive:)ha la seguente dichiarazione:

Chiedere al delegato se un riconoscitore di gesti deve ricevere un oggetto che rappresenta un tocco.

optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool

Il codice completo di seguito mostra una possibile implementazione per gestureRecognizer(_:shouldReceive:). Con questo codice, un tocco su una sottoview della ViewControllervista di (inclusa imageView) non attiverà il printHello(_:)metodo.

import UIKit

class ViewController: UIViewController, UIGestureRecognizerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(printHello))
        tapGestureRecognizer.delegate = self
        view.addGestureRecognizer(tapGestureRecognizer)

        let imageView = UIImageView(image: UIImage(named: "icon")!)
        imageView.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
        view.addSubview(imageView)

        // ⚠️ Enable user interaction for imageView so that it can participate to touch events.
        // Otherwise, taps on imageView will be forwarded to its superview and managed by it.
        imageView.isUserInteractionEnabled = true
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        // Prevent subviews of a specific view to send touch events to the view's gesture recognizers.
        if let touchedView = touch.view, let gestureView = gestureRecognizer.view, touchedView.isDescendant(of: gestureView), touchedView !== gestureView {
            return false
        }
        return true
    }

    @objc func printHello(_ sender: UITapGestureRecognizer) {
        print("Hello")
    }

}

Un'implementazione alternativa di gestureRecognizer(_:shouldReceive:)può essere:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return gestureRecognizer.view === touch.view
}

Si noti tuttavia che questo codice alternativo non controlla se touch.view è una sottoview di gestureRecognizer.view.


10

Soluzione rapida completa (il delegato deve essere implementato E impostato per i riconoscitori):

class MyViewController: UIViewController UIGestureRecognizerDelegate {

    override func viewDidLoad() {
        let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(onBaseTapOnly))
        doubleTapRecognizer.numberOfTapsRequired = 2
        doubleTapRecognizer.delegate = self
        self.view.addGestureRecognizer(doubleTapRecognizer)
    }

    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
        if touch.view.isDescendantOfView(self.view){
            return false
        }
        return true
    }

    func onBaseTapOnly(sender: UITapGestureRecognizer) {
        if sender.state == .Ended {
            //react to tap
        }
    }
}

6

Variante usando CGPoint che tocchi (SWIFT 4.0)

class MyViewController: UIViewController, UIGestureRecognizerDelegate {

  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {

// Get the location in CGPoint
    let location = touch.location(in: nil)

// Check if location is inside the view to avoid
    if viewToAvoid.frame.contains(location) {
        return false
    }

    return true
  }
}

4

Cancella modo rapido

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view == self.view
}

Non viene mai chiamato in iOS 13, almeno con swipeGestureRecognizer.
Chewie The Chorkie

In che modo questo è diverso dalla risposta di Ian?
sweetfa

3

Tieni presente che l' API gestureRecognizer è cambiata in:

gestureRecognizer (_: shouldReceive :)

Prendere nota in particolare dell'indicatore di sottolineatura (skip) per l'etichetta esterna del primo parametro.

Utilizzando molti degli esempi forniti sopra, non stavo ricevendo l'evento. Di seguito è riportato un esempio che funziona per le versioni correnti di Swift (3+).

public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    var shouldReceive = false
    if let clickedView = touch.view {
        if clickedView == self.view {
            shouldReceive = true;
        }
    }
    return shouldReceive
}

2

Oltre alle soluzioni di cui sopra, non dimenticare di controllare la User Interaction Enabledtua vista secondaria.

inserisci qui la descrizione dell'immagine


2

Ho dovuto impedire il gesto sulla vista del bambino. L'unica cosa che ha funzionato è consentire e mantenere la prima visualizzazione e impedire il gesto in tutte le visualizzazioni successive:

   var gestureView: UIView? = nil

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if (gestureView == nil || gestureView == touch.view){
            gestureView = touch.view
            return true
        }
        return false
     }

1

Swift 4:

touch.view è ora un optional, quindi in base alla risposta di @ Antoine:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if let touchedView = touch.view, touchedView.isDescendant(of: deductibleBackgroundView) {
        return false
    }
    return true
}

0

Se non si desidera che il 'doppio-tap riconoscitore' in conflitto con i pulsanti e / o altri controlli, è possibile impostare selfcome UIGestureRecognizerDelegatee implementare:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
{
    return !(touch.view is UIControl)
}
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.