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];
Matrosov Alexander
źródło
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
}
Ravi
źródło
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
}
Ian
źródło
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.

Antoine
źródło
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.

Imanou Petit
źródło
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
        }
    }
}
Arru
źródło
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
  }
}
CoachThys
źródło
4

Wyczyść Swift sposób

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view == self.view
}
Musa almatri
źródło
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
}
Rico
źródło
2

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

wprowadź opis obrazu tutaj

MrDEV
źródło
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
     }
Shay Moreno
źródło
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
}
Jonathan Cabrera
źródło
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)
}
PDK
źródło