UIGestureRecognizer blokuje widok podrzędny do obsługi zdarzeń dotykowych

82

Próbuję dowiedzieć się, jak to się robi we właściwy sposób . Próbowałem zobrazować sytuację: wprowadź opis obrazu tutaj

Dodaję UITableViewjako podrzędny od a UIView. Do UIViewreaguje na zaczepów i pinchGestureRecognizer, ale gdy robi tak, tableview przestaje reagować na tych dwóch gestów (wciąż reaguje na kiepskie piwo).

Zrobiłem to z następującym kodem, ale oczywiście nie jest to przyjemne rozwiązanie i jestem pewien, że jest lepszy sposób. Jest to umieszczane w UIView(nadzorze):

-(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];
}
andershqst
źródło

Odpowiedzi:

182

Miałem bardzo podobny problem i znalazłem rozwiązanie w tym pytaniu SO . Podsumowując, ustaw siebie jako delegata dla swojego, UIGestureRecognizera następnie sprawdź docelowy widok, zanim zezwolisz aparatowi rozpoznającemu na przetworzenie dotyku. Odpowiednia metoda delegata to:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch
Justin
źródło
3
Najbardziej podoba mi się to rozwiązanie, ponieważ nie wymaga majstrowania przy dotknięciach hitTest:withEvent:lub pointInside:withEvent:.
DarkDust,
6
Czyste rozwiązanie, jak możesz przetestować np. return !(touch.view == givenView);Jeśli chcesz po prostu wykluczyć dany widok lub return !(touch.view.tag == kTagNumReservedForExcludingViews);gdy chcesz zatrzymać aparat rozpoznający przetwarzający dotyk na całej grupie różnych podwidoków.
cate
6
Zrobiłbym test trafień z - (BOOL)isDescendantOfView:(UIView *)view. Działa to również dobrze w - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)eventprzypadku podklasy UIGestureRecognizer.
Christoph
1
@Justin, co jeśli nasz aparat do rozpoznawania gestów należy do uiscrollview i nie możemy delegować funkcji rozpoznawania gestów?
Gökhan Barış Aker
109

Domyślnym zachowaniem jest blokowanie zdarzeń dotyku w widokach podrzędnych. Możesz zmienić to zachowanie:

UITapGestureRecognizer *r = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(agentPickerTapped:)];
r.cancelsTouchesInView = NO;
[agentPicker addGestureRecognizer:r];
Clive Paterson
źródło
5
Spowoduje to wysłanie zdarzeń dotyku do podglądów podrzędnych, ale zostaną one również przesłane do modułu rozpoznawania gestów. Zapobiegnie to więc blokowaniu podglądów podrzędnych, ale aparat rozpoznawania gestów będzie nadal rozpoznawany w widokach podrzędnych.
Jonathan.
@Jonathan. Tak, zgadzam się z wami. To, co zrobiłem w mojej metodzie obsługi gestów, to sprawdzenie, czy lokalizacja gestu ma miejsce w odpowiednim podwidoku, w którym to przypadku nie muszę wykonywać pozostałej części kodu. Ponadto jednym z powodów, dla których wybrałem to obejście, jest to, że UITapGestureRecognizer nie deklaruje translationInViewmetody. Dlatego implementacja wspomnianej powyżej metody UIGestureRecognizerDelegate spowodowałaby jedynie awarię z błędem ... unrecognized selector sent to blah. Aby sprawdzić, używać coś takiego: CGRectContainsPoint(subview.bounds, [recognizer locationInView:subview]).
MkVal
Zgodnie z pytaniem OP tę odpowiedź należy wybrać jako główną odpowiedź.
farzadshbfn
5

Wyświetlałem rozwijany widok podrzędny, który miał własny widok tabeli. W rezultacie touch.viewczasami zwracały klasy, takie jak UITableViewCell. Musiałem przejść przez superklasę (y), aby upewnić się, że jest to podklasa, o której myślałem:

-(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;
}
joslinm
źródło
Pętla while odpowiada za to
joslinm
5

Opierając się na @Pin Shih Wang odpowiedź . Ignorujemy wszystkie dotknięcia inne niż te w widoku zawierającym rozpoznawanie gestów dotknięcia. Wszystkie dotknięcia są przekazywane do hierarchii widoków tak normalnie, jak ustawiliśmy tapGestureRecognizer.cancelsTouchesInView = false. Oto kod w 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)
    }
}
Nick Ager
źródło
Jak mogę porównać aparat rozpoznawania gestów z UISwipeActionStandardButton? Próbowałem z.some(UISwipeActionStandardButton)
Jalil
4

Jedną z możliwości jest utworzenie podklasy aparatu rozpoznawania gestów (jeśli jeszcze tego nie zrobiłeś) i zastąpienie go -touchesBegan:withEvent:tak, aby określał, czy każdy dotyk rozpoczął się w wykluczonym podglądzie i wywołuje -ignoreTouch:forEvent:ten dotyk, jeśli tak się stało.

Oczywiście musisz również dodać właściwość, aby śledzić wykluczony podwidok lub, być może lepiej, tablicę wykluczonych podglądów podrzędnych.

Caleb
źródło
Jest mnóstwo kodu tutaj github.com/…
johndpope
2

Można to zrobić bez dziedziczenia żadnej klasy.

Możesz sprawdzić gestRecognizers w selektorze wywołań zwrotnych gestów

jeśli view.gestureRecognizers nie zawiera Twojego gestRecognizer, po prostu go zignoruj

na przykład

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

sprawdź tutaj 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
    }
}
Przypnij Shih Wang
źródło
Jak wspomniano w innych odpowiedziach, jeśli używasz tej metody, upewnij się, że mechanizm rozpoznawania gestów stuknięcia przekazuje kran do hierarchii widoku z: singleTapGesture.cancelsTouchesInView = NO;dodany do viewDidLoadpowyżej
Nick Ager
1

Stworzyłem podklasę UIGestureRecognizer przeznaczoną do blokowania wszystkich aparatów rozpoznawania gestów dołączonych do superwizji określonego widoku.

To część mojego projektu WEPopover. Znajdziesz go tutaj .

Werner Altewischer
źródło
1

zaimplementuj delegata dla wszystkich modułów rozpoznawania elementu parentView i umieść metodę gestRecognizer w delegacie, który jest odpowiedzialny za jednoczesne wyzwalanie modułów rozpoznawania:

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

}

Możesz użyć metod niepowodzenia, jeśli chcesz, aby dzieci były wyzwalane, ale nie nadrzędne rozpoznawanie:

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

user7270881
źródło
0

Robiłem też popover i tak to zrobiłem

func didTap(sender: UITapGestureRecognizer) {

    let tapLocation = sender.locationInView(tableView)

    if let _ = tableView.indexPathForRowAtPoint(tapLocation) {
        sender.cancelsTouchesInView = false
    }
    else {
        delegate?.menuDimissed()
    }
}
Maria
źródło
0

Możesz to wyłączyć i włączyć .... w moim kodzie zrobiłem coś takiego, ponieważ musiałem to wyłączyć, gdy klawiatura się nie wyświetlała, możesz zastosować to do swojej sytuacji:

nazwij to viewdidload itp:

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

następnie utwórz dwie metody:

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

To powoduje wyłączenie kranu. Musiałem jednak zmienić tap w zmienną instancji i zwolnić ją w trybie dealloc.

j2emanue
źródło