UIGestureRecognizer blockiert die Unteransicht für die Behandlung von Berührungsereignissen


82

Ich versuche herauszufinden, wie das richtig gemacht wird . Ich habe versucht, die Situation darzustellen: Geben Sie hier die Bildbeschreibung ein

Ich UITableViewfüge a als Unteransicht von a hinzu UIView. Der UIViewreagiert auf ein Tippen und pinchGestureRecognizer, aber wenn er dies tut, reagiert die Tabellenansicht nicht mehr auf diese beiden Gesten (sie reagiert immer noch auf Wischen).

Ich habe es mit dem folgenden Code zum Laufen gebracht, aber es ist offensichtlich keine gute Lösung und ich bin sicher, dass es einen besseren Weg gibt. Dies wird in die UIView(Übersicht) gestellt:

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if([super hitTest:point withEvent:event] == self) {
        for (id gesture in self.gestureRecognizers) {
            [gesture setEnabled:YES];
        }
        return self;
    }
    for (id gesture in self.gestureRecognizers) {
        [gesture setEnabled:NO];
    }
    return [self.subviews lastObject];
}

Antworten:


182

Ich hatte ein sehr ähnliches Problem und fand meine Lösung in dieser SO-Frage . Zusammenfassend können Sie sich als Delegierter für Ihre festlegen UIGestureRecognizerund dann die Zielansicht überprüfen, bevor Ihr Erkenner die Berührung verarbeiten kann. Die relevante Delegate-Methode lautet:

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

3
Ich mag diese Lösung am meisten, da es nicht darum geht, mit den Berührungen herumzuspielen, hitTest:withEvent:oder pointInside:withEvent:.
DarkDust

6
Saubere Lösung, mit der Sie testen können, z. B. return !(touch.view == givenView);ob Sie nur eine bestimmte Ansicht ausschließen möchten oder ob Sie verhindern return !(touch.view.tag == kTagNumReservedForExcludingViews);möchten, dass Ihr Erkenner die Berührung einer ganzen Reihe verschiedener Unteransichten verarbeitet.
Cate

6
Ich würde den Hit-Test mit machen - (BOOL)isDescendantOfView:(UIView *)view. Dies funktioniert auch gut, - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)eventwenn UIGestureRecognizer untergeordnet wird.
Christoph

1
@ Justin, was ist, wenn unser Gestenerkenner zu uiscrollview gehört und wir die Gestenerkenner nicht delegieren können?
Gökhan Barış Aker

108

Das Blockieren von Berührungsereignissen für Unteransichten ist das Standardverhalten. Sie können dieses Verhalten ändern:

UITapGestureRecognizer *r = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(agentPickerTapped:)];
r.cancelsTouchesInView = NO;
[agentPicker addGestureRecognizer:r];

5
Dadurch werden die Berührungsereignisse an die Unteransichten gesendet, aber auch an die Gestenerkennung. Dies verhindert also das Blockieren der Unteransichten, aber die Gestenerkennung wird in Unteransichten weiterhin erkannt.
Jonathan.

@ Jonathan. Ja, ich stimme dir zu. Bei meiner Gestenbehandlungsmethode habe ich überprüft, ob der Gestenort in der betreffenden Unteransicht liegt. In diesem Fall muss der Rest des Codes nicht ausgeführt werden. Ein Grund, warum ich mich für diese Problemumgehung entschieden habe, ist, dass UITapGestureRecognizer keine translationInViewMethode deklariert . Die Implementierung der oben genannten UIGestureRecognizerDelegate-Methode würde daher nur zu einem Absturz mit Fehler führen ... unrecognized selector sent to blah. Verwenden Sie zur Überprüfung Folgendes : CGRectContainsPoint(subview.bounds, [recognizer locationInView:subview]).
MkVal

Gemäß der Frage von OP sollte diese Antwort als Hauptantwort ausgewählt werden.
Farzadshbfn

5

Ich habe eine Dropdown-Unteransicht mit einer eigenen Tabellenansicht angezeigt. Infolgedessen touch.viewwürden die manchmal Klassen wie zurückgeben UITableViewCell. Ich musste die Oberklasse (n) durchlaufen, um sicherzustellen, dass es die Unterklasse war, die ich dachte:

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    UIView *view = touch.view;
    while (view.class != UIView.class) {
        // Check if superclass is of type dropdown
        if (view.class == dropDown.class) { // dropDown is an ivar; replace with your own
            NSLog(@"Is of type dropdown; returning NO");
            return NO;
        } else {
            view = view.superview;
        }
    }

    return YES;
}

Die while-Schleife erklärt das
Joslinm

4

Eine Möglichkeit besteht darin, Ihren Gestenerkenner zu unterordnen (falls Sie dies noch nicht -touchesBegan:withEvent:getan haben) und ihn so zu überschreiben , dass er bestimmt, ob jede Berührung in einer ausgeschlossenen Unteransicht begonnen hat, und -ignoreTouch:forEvent:diese Berührung anfordert , wenn dies der Fall ist .

Natürlich müssen Sie auch eine Eigenschaft hinzufügen, um die ausgeschlossene Unteransicht oder besser ein Array ausgeschlossener Unteransichten zu verfolgen.


Es gibt
jede Menge

4

Aufbauend auf der Antwort von @Pin Shih Wang . Wir ignorieren alle Taps außer denen in der Ansicht, die den Tap-Gestenerkenner enthält. Alle Taps werden wie gewohnt an die Ansichtshierarchie weitergeleitet tapGestureRecognizer.cancelsTouchesInView = false. Hier ist der Code in Swift3 / 4:

func ensureBackgroundTapDismissesKeyboard() {
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
    tapGestureRecognizer.cancelsTouchesInView = false
    self.view.addGestureRecognizer(tapGestureRecognizer)
}

@objc func handleTap(recognizer: UIGestureRecognizer) {
    let location = recognizer.location(in: self.view)
    let hitTestView = self.view.hitTest(location, with: UIEvent())
    if hitTestView?.gestureRecognizers?.contains(recognizer) == .some(true) {
        // I dismiss the keyboard on a tap on the scroll view
        // REPLACE with own logic
        self.view.endEditing(true)
    }
}

2

Es ist möglich, auf das Erben einer Klasse zu verzichten.

Sie können gestureRecognizers in der Rückrufauswahl von gesture überprüfen

Wenn view.gestureRecognizers Ihren gestureRecognizer nicht enthält, ignorieren Sie ihn einfach

beispielsweise

- (void)viewDidLoad
{
    UITapGestureRecognizer *singleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self     action:@selector(handleSingleTap:)];
    singleTapGesture.numberOfTapsRequired = 1;
}

Überprüfen Sie hier view.gestureRecognizers

- (void)handleSingleTap:(UIGestureRecognizer *)gestureRecognizer
{
    UIEvent *event = [[UIEvent alloc] init];
    CGPoint location = [gestureRecognizer locationInView:self.view];

    //check actually view you hit via hitTest
    UIView *view = [self.view hitTest:location withEvent:event];

    if ([view.gestureRecognizers containsObject:gestureRecognizer]) {
        //your UIView
        //do something
    }
    else {
        //your UITableView or some thing else...
        //ignore
    }
}

Wie in anderen Antworten erwähnt, stellen Sie bei Verwendung dieser Methode sicher, dass der Tippgestenerkenner den Tipp an Ihre Ansichtshierarchie weiterleitet mit: singleTapGesture.cancelsTouchesInView = NO;hinzugefügt zu viewDidLoadoben
Nick Ager

1

Ich habe eine UIGestureRecognizer-Unterklasse erstellt, mit der alle Gestenerkenner blockiert werden können, die an Übersichten einer bestimmten Ansicht angehängt sind.

Es ist Teil meines WEPopover-Projekts. Sie finden es hier .


1

Implementieren Sie einen Delegaten für alle Erkenner der übergeordneten Ansicht und fügen Sie die gestureRecognizer-Methode in den Delegaten ein, der für das gleichzeitige Auslösen von Erkennern verantwortlich ist:

func gestureRecognizer(UIGestureRecognizer,       shouldBeRequiredToFailByGestureRecognizer:UIGestureRecognizer) -> Bool {
if (otherGestureRecognizer.view.isDescendantOfView(gestureRecognizer.view)) {
    return true
    } else {
    return false
}

}}

Sie können die Fehlermethoden verwenden, wenn Sie möchten, dass die untergeordneten Elemente ausgelöst werden, nicht jedoch die übergeordneten Erkenner:

https://developer.apple.com/reference/uikit/uigesturerecognizerdelegate


0

Ich habe auch einen Popover gemacht und so habe ich es gemacht

func didTap(sender: UITapGestureRecognizer) {

    let tapLocation = sender.locationInView(tableView)

    if let _ = tableView.indexPathForRowAtPoint(tapLocation) {
        sender.cancelsTouchesInView = false
    }
    else {
        delegate?.menuDimissed()
    }
}

0

Sie können es aus- und wieder einschalten. In meinem Code habe ich so etwas getan, da ich es ausschalten musste, wenn die Tastatur nicht angezeigt wurde. Sie können es auf Ihre Situation anwenden:

Nennen Sie dies viewdidload usw.:

NSNotificationCenter    *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(notifyShowKeyboard:) name:UIKeyboardDidShowNotification object:nil];
[center addObserver:self selector:@selector(notifyHideKeyboard:) name:UIKeyboardWillHideNotification object:nil];

Erstellen Sie dann die beiden Methoden:

-(void) notifyShowKeyboard:(NSNotification *)inNotification 
{
    tap.enabled=true;  // turn the gesture on
}

-(void) notifyHideKeyboard:(NSNotification *)inNotification 
{
    tap.enabled=false;  //turn the gesture off so it wont consume the touch event
}

Dadurch wird der Wasserhahn deaktiviert. Ich musste tap in eine Instanzvariable verwandeln und sie im Dealloc freigeben.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.