UITapGestureRecognizer dotknij self.view, ale ignoruj ​​podglądy


96

Muszę zaimplementować funkcję, która będzie wywoływać kod po dwukrotnym dotknięciu self.view (widoku UIViewCotroller). Ale problem polega na tym, że mam inny obiekt UI w tym widoku i nie chcę dołączać żadnego obiektu rozpoznawania do wszystkich z nich. Poniżej znalazłem tę metodę, jak wykonać gest na moim widoku i wiem, jak to działa. W tej chwili stoję przed handicapem, który sposób wybrać, aby utworzyć ten aparat rozpoznający, ignorując podgląd podrzędny. Jakieś pomysły? Dzięki.

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

1
Nie jestem pewien, ale czy próbowałeś ustawić cancelsTouchesInView na NO w aparacie rozpoznawania? więc [doubleTap setCancelsTouchesInView: NO];
JDx

Odpowiedzi:


146

Powinieneś przyjąć UIGestureRecognizerDelegateprotokół wewnątrz selfobiektu i wywołać poniższą metodę sprawdzania widoku. Wewnątrz tej metody sprawdź swój widok touch.viewi zwróć odpowiednią wartość bool (Tak / Nie). Coś takiego:

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

Edycja: Proszę, sprawdź także odpowiedź @ Ian!

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

jeszcze jedno pytanie Mam na widoku kilka przycisków, które muszą działać. jak mogę ustawić dla nich jakiś priorytet?
Matrosov Alexander

jeśli twoje przyciski mają własne rozpoznawanie gestów (prawdopodobnie), zagłęb się w ich wewnętrzne elementy i chwyć je, i przeczytaj dokumentację, -[UIGestureRecognizer requireGestureRecognizerToFail:]ale może to zadziałać bez ich
modyfikowania

szukał tego godzinami! Dzięki! ;)
MasterRazer

Jeśli iftest się nie powiedzie, Twoja implementacja nie zwróci wartości BOOL; dla właściwego stylu zwraca TAK po bloku if (użycie { }) lub w elsegałęzi. Jednak dzięki, oszczędziłem trochę czytania.
RobP

2
Widok jest potomkiem samego siebie, więc to nie działa ... (ogólne podejście jest w porządku, zobacz inną odpowiedź)
Ixx

109

Innym podejściem jest po prostu porównanie, czy widok dotyku jest widokiem gestów, ponieważ potomkowie nie przejdą tego warunku. Ładny, prosty jednolinijkowy:

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

2
To działa lepiej dla mnie niż zaakceptowana odpowiedź. O wiele bardziej zwięzłe.
Suman Roy

1
@Ian ta metoda nie jest wywoływana po dotknięciu widoku.
Praburaj

1
Świetna zwięzła odpowiedź!
scurioni

Przyjęta odpowiedź nawet nie zadziałała, ale to zadziałało bezbłędnie.
Shalin Shah

@Prabu prawdopodobnie dlatego, że delegat rozpoznawania gestów musi być ustawiony wcześniej
Kubba

23

A dla wariantu Swift:

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

Warto wiedzieć, isDescendantOfViewzwraca Booleanwartość wskazującą, czy odbiornik jest podziałem danego widoku, czy identycznym z tym widokiem.


1
Nie zapomnij ustawić UIGestureRecognizerDelegate w swojej klasie!
Fox5150

4
Zamiast robić to, jeśli oświadczenie, czy nie możesz tego po prostu zwrócić? return! touch.view.isDescendant (of: gestureRecognizer.view) Ponadto nowa składnia w swift 3 ^^
EdwardSanchez

13

W Swift 5 i iOS 12 UIGestureRecognizerDelegatema metodę o nazwie gestureRecognizer(_:shouldReceive:). gestureRecognizer(_:shouldReceive:)posiada następującą deklarację:

Zapytaj delegata, czy aparat rozpoznawania gestów powinien otrzymać obiekt reprezentujący dotyk.

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

Pełny kod poniżej przedstawia możliwą implementację dla gestureRecognizer(_:shouldReceive:). W przypadku tego kodu stuknięcie w podwidok ViewControllerwidoku (w tym imageView) nie wyzwoli printHello(_:)metody.

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

}

Alternatywną implementacją gestureRecognizer(_:shouldReceive:)może być:

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

Należy jednak pamiętać, że ten alternatywny kod nie sprawdza, czy touch.viewjest to podwidok gestureRecognizer.view.


10

Kompletne szybkie rozwiązanie (delegat musi być zaimplementowany ORAZ ustawiony dla aparatów rozpoznających):

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

Wariant wykorzystujący CGPoint you touch (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

Wyczyść Swift sposób

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

Nigdy nie jest wywoływany w iOS 13, przynajmniej za pomocą swipeGestureRecognizer.
Chewie The Chorkie

Czym to się różni od odpowiedzi Iana?
sweetfa

3

Zwróć uwagę, że interfejs API GestRecognizer zmienił się na:

gestRecognizer (_: shouldReceive :)

Zwróć szczególną uwagę na wskaźnik podkreślenia (pominięcia) dla zewnętrznej etykiety pierwszego parametru.

Korzystając z wielu przykładów podanych powyżej, nie otrzymałem wydarzenia. Poniżej znajduje się przykład, który działa dla aktualnych wersji 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

Plus powyższe rozwiązania, nie zapomnij sprawdzić User Interaction Enabledswojego podglądu.

wprowadź opis obrazu tutaj


2

Musiałem zapobiec gestowi w widoku dziecka. Jedyne, co zadziałało, to zezwolenie i zachowanie pierwszego widoku oraz zapobieganie gestom we wszystkich następnych widokach:

   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 jest teraz opcjonalne, więc na podstawie odpowiedzi @ Antoine:

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

0

Jeśli nie chcesz, aby „rozpoznawanie podwójnego dotknięcia” powodowało konflikt z przyciskami i / lub innymi kontrolkami, możesz ustawić selfjako UIGestureRecognizerDelegatei zaimplementować:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
{
    return !(touch.view is UIControl)
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.