UITapGestureRecognizer psuje UITableView didSelectRowAtIndexPath

207

Napisałem własną funkcję przewijania pól tekstowych w górę, gdy pojawia się klawiatura. Aby zamknąć klawiaturę, dotykając pola tekstowego, stworzyłem taki, UITapGestureRecognizerktóry dba o rezygnację z pierwszej odpowiedzi na polu tekstowym po stuknięciu.

Teraz utworzyłem również autouzupełnianie dla UITableViewpola tekstowego, które tworzy pole tuż pod polem tekstowym i wypełnia je elementami, gdy użytkownik wprowadza tekst.

Jednak po wybraniu jednego z wpisów w tabeli autouzupełniania didSelectRowAtIndexPathnie zostanie wywołany. Zamiast tego wydaje się, że rozpoznawany jest gest rozpoznawania gestów i po prostu rezygnuje z pierwszej odpowiedzi.

Domyślam się, że jest jakiś sposób, aby powiedzieć narzędziu rozpoznającemu gest gestu, aby dalej przekazywał komunikat o dotknięciu do UITableView, ale nie mogę zrozumieć, co to jest. Każda forma pomocy jest mile widziana.

Jason
źródło
Zobacz ten wątek .
Duncan Babbage,

Odpowiedzi:

258

Ok, w końcu znalazłem to po przeszukaniu dokumentacji rozpoznawania gestów.

Rozwiązaniem było wdrożenie UIGestureRecognizerDelegatei dodanie następujących elementów:

////////////////////////////////////////////////////////////
// UIGestureRecognizerDelegate methods

#pragma mark UIGestureRecognizerDelegate methods

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if ([touch.view isDescendantOfView:autocompleteTableView]) {

        // Don't let selections of auto-complete entries fire the 
        // gesture recognizer
        return NO;
    }

    return YES;
}

To załatwiło sprawę. Mam nadzieję, że pomoże to również innym.

Jason
źródło
Prawdopodobnie musisz także pominąć stuknięcia w polu tekstowym.
Steven Kramer
5
Miałem więcej szczęściareturn ![touch.view isKindOfClass:[UITableViewCell class]];
Josh Paradroid
1
@Josh To nie działa, jeśli klikniesz etykiety wewnątrz komórki. Właściwie odpowiedź Jasona działa idealnie na to, czego potrzebuję.
William T.
Ale to nie jest odpowiedź na twój problem, nie? To jest albo / lub. Zasadniczo każdy dotyk w widoku tabeli jest nieistotny w rozpoznawaniu gestów .... to, czego chcesz, ZARÓWNO rozpoznawanie gestów ORAZ wybór widoku tabeli do pracy
PostCodeism
5
@Durgaprasad Aby wyraźnie wyłączyć rozpoznawanie gestów podczas wybierania komórek, dodałem ![touch.view isEqual: self.tableView] &&przed, [touch.view isDescendantOfView: self.tableView]aby wykluczyć tableViewsam (ponieważ isDescendantOfView:zwraca również, YESjeśli argument-widok jest samym widokiem-odbiorcy). Mam nadzieję, że pomaga innym ludziom, którzy chcą tego samego rezultatu.
anneblue
220

Najprostszym sposobem rozwiązania tego problemu jest:

UITapGestureRecognizer *tapRec = [[UITapGestureRecognizer alloc] 
    initWithTarget:self action:@selector(tap:)];
[tapRec setCancelsTouchesInView:NO];

Pozwala to UIGestureRecognizerrozpoznać stuknięcie, a także przekazać dotyk następnemu respondentowi. Niezamierzoną konsekwencją tej metody jest to, że masz UITableViewCellekran, który popycha inny kontroler widoku. Jeśli użytkownik stuknie wiersz, aby zamknąć klawiaturę, zarówno klawiatura, jak i push zostaną rozpoznane. Wątpię, aby to było to, co zamierzasz, ale ta metoda jest odpowiednia w wielu sytuacjach.

Ponadto, rozwijając odpowiedź Roberta, jeśli masz wskaźnik do widoku tabeli, o którym mowa, możesz bezpośrednio porównać jego klasę zamiast konwertować na ciąg znaków i mam nadzieję, że Apple nie zmieni nazewnictwa:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
     shouldReceiveTouch:(UITouch *)touch
{
    if([touch.view class] == tableview.class){
        return //YES/NO
    }

    return //YES/NO

}

Pamiętaj, że musisz również zadeklarować, UIGestureRecognizeraby mieć delegata z tym kodem.

TMilligan
źródło
4
Dzięki, setCancelsTouchesInView zmienia wszystko :)
PostCodeism
Może nadal chcesz używać isDescendantOfViewzamiast sprawdzania, czy wartość classjest równa, ponieważ możesz stuknąć w widok podrzędny. Zależy od tego, czego potrzebujesz.
shim
71

Ustaw cancelsTouchesInVieww rozpoznawaniu wartość false. W przeciwnym razie sam „zużywa” dotyk i nie przekazuje go do widoku tabeli. Dlatego nigdy nie odbywa się selekcja.

na przykład w swift

let tapOnScreen: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "CheckTheTime")
tapOnScreen.cancelsTouchesInView = false
view.addGestureRecognizer(tapOnScreen)
Abdulrahman Masoud
źródło
1
Dzięki za to!
Bishan,
1
To najczystszy w Swift. Dzięki
Dx_
działa dobrze w komórkach w widoku tabeli, piękny i prosty!
Abdoelrhman
@laoyur bardzo prawda
BLACK HoL
nadal
dostaję
42

I dla Swift (na podstawie odpowiedzi @Jason):

class MyAwesomeClass: UIViewController, UIGestureRecognizerDelegate

private var tap: UITapGestureRecognizer!

override func viewDidLoad() {
   super.viewDidLoad()

   self.tap = UITapGestureRecognizer(target: self, action: "viewTapped:")
   self.tap.delegate = self
   self.view.addGestureRecognizer(self.tap)
}

// UIGestureRecognizerDelegate method
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if touch.view?.isDescendantOfView(self.tableView) == true {
        return false
    }
    return true
}
Nikolaj Nielsen
źródło
Dzięki bardzo zaoszczędziłem mój czas.
Bishan,
22

Mogę mieć lepsze rozwiązanie, aby dodać gest stuknięcia w widoku tabeli, ale jednocześnie umożliwiając wybór komórki:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if gestureRecognizer is UITapGestureRecognizer {
        let location = touch.locationInView(tableView)
        return (tableView.indexPathForRowAtPoint(location) == nil)
    }
    return true
}

Po prostu szukam komórki w punkcie ekranu, w którym użytkownik stuka. Jeśli nie znaleziono ścieżki indeksu, pozwalam gestowi na dotknięcie, w przeciwnym razie go anuluję. Dla mnie działa świetnie.

bonokit
źródło
1
To rozwiązanie kołysze się! Bardzo przydatne do odróżnienia komórki od pustego pola tabeli.
Alessandro Ornano
18

Myślę, że nie ma potrzeby zapisu bloków kodów po prostu zestaw cancelsTouchesInViewdo falsedla obiektu gest, domyślnie jest to truei po prostu trzeba go ustawić false. Jeśli używasz UITapGestureobiektu w kodzie, a także używasz UIScrollView(widok tabeli, widok kolekcji), ustaw tę właściwośćfalse

let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
SHUBHAYAN KUNDU
źródło
Dzięki za opublikowanie tej odpowiedzi
Gustavo Baiocchi Costa
14

Podobnym rozwiązaniem jest wdrożenie gestureRecognizer:shouldReceiveTouch: za pomocą klasy widoku w celu ustalenia, jakie działanie należy podjąć. Zaletą tego podejścia jest nie maskowanie zaczepów w regionie bezpośrednio otaczającym tabelę (widoki tych obszarów nadal pochodzą z instancji UITableView, ale nie reprezentują komórek).

Ma to również tę zaletę, że działa z wieloma tabelami w jednym widoku (bez dodawania dodatkowego kodu).

Zastrzeżenie: zakłada się, że Apple nie zmieni nazwy klasy.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    return ![NSStringFromClass([touch.view class]) isEqualToString:@"UITableViewCellContentView"];
}
Robert Altman
źródło
1
Aby wyeliminować ciąg nazwy klasy, użyj tego wierszareturn ![[touch.view.superview class] isSubclassOfClass:[UITableViewCell class]];
Nianliang
7

W przypadku Swift 4.2 rozwiązaniem było wdrożenie UIGestureRecognizerDelegatei dodanie następujących elementów:

extension ViewController : UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if touch.view!.isDescendant(of: tblView) {
            return false
        }
        return true
    }
}

po kliknięciu widoku tabeli ta metoda delegowania zwraca wartość false, a didSelectRowAtIndexPathmetoda widoku tabeli działa poprawnie.

Ashish Kanani
źródło
1
dzięki kolego za opublikowanie tej odpowiedzi, oszczędzasz mój czas.
Jay Bhalani
4

Miałem inną sytuację, w której chciałem, aby funkcja gestu dotykowego była wywoływana tylko wtedy, gdy użytkownik stuknął poza widok tabeli. Jeśli użytkownik dotknął wewnątrz widoku tabeli, nie należy wywoływać funkcji gestu dotykowego. Dodatkowo, jeśli wywoływana jest funkcja gestu dotykowego, powinna ona nadal przekazywać zdarzenie dotykowe do widoku, który został kliknięty, zamiast go zużywać.

Wynikowy kod jest kombinacją odpowiedzi Abdulrahmana Masouda i odpowiedzi Nikolaja Nielsena.

extension MyViewController: UIGestureRecognizerDelegate {
    func addGestureRecognizer() {
        let tapOnScreen = UITapGestureRecognizer(target: self,
                                                action: #selector(functionToCallWhenUserTapsOutsideOfTableView))

        // stop the gesture recognizer from "consuming" the touch event, 
        // so that the touch event can reach other buttons on view.
        tapOnScreen.cancelsTouchesInView = false

        tapOnScreen.delegate = self

        self.view.addGestureRecognizer(tapOnScreen)
    }

    // if your tap event is on the menu, don't run the touch event.
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if touch.view?.isDescendant(of: self.tableView) == true {
            return false
        }
        return true
    }

    @objc func functionToCallWhenUserTapsOutsideOfTableView() {

        print("user tapped outside table view")
    }
}

A w MyViewControllerklasie, w klasie UITableView, w której onViewDidLoad(), zadzwoniłem addGestureRecognizer():

class MyViewController: UIViewController {
    ...
    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        self.addGestureRecognizer()
        ...
    }
    ...
}
Ali Mizan
źródło
3

Wystarczy ustawić cancels touches in view, aby falsena każdym geście poniżej (table/collection)View:

- Konstruktor interfejsów

Konstruktor interfejsów

- Kod

<#gestureRecognizer#>.cancelsTouchesInView = false
Mojtaba Hosseini
źródło
co jeśli chcę wyłączyć, to działa nie tylko pozwalając didSelectRowAt na pracę z nim?
Eyad Shokry
1

Chociaż jest późno i wiele osób uważa, że ​​powyższe sugestie działają dobrze, nie mogłem zmusić metod Jasona lub TMilligan do działania.

Mam Statyczny tableView z wieloma komórkami zawierającymi pola tekstowe, które odbierają dane liczbowe za pomocą tylko klawiatury numerycznej. To było dla mnie idealne:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if(![touch.view isKindOfClass:[UITableViewCell class]]){

        [self.firstTF resignFirstResponder];
        [self.secondTF resignFirstResponder];
        [self.thirdTF resignFirstResponder];
        [self.fourthTF resignFirstResponder];

        NSLog(@"Touches Work ");

        return NO;
    }
    return YES;
}

Upewnij się, że zaimplementowałeś to <UIGestureRecognizerDelegate>w pliku .h.

Ten wiersz ![touch.view isKindOfClass:[UITableViewCell class]]sprawdza, czy podsłuchiwano tableViewCell, i usuwa aktywną klawiaturę.

App Dev Guy
źródło
1

Prostym rozwiązaniem jest użycie instancji UIControl w UITableViewCell, aby uzyskać poprawki. Możesz dodać dowolne widoki za pomocą userInteractionEnables == NO do UIControl, aby uzyskać tapy.

Andrzej Romanow
źródło
0

Oto moje rozwiązanie, które łączy bezpośrednio moduł rozpoznawania rozpoznawania z tym, czy klawiatura się wyświetla.

W funkcji rozpoznawania gestów dotknij deleguj:

#pragma mark - UIGestureRecognizerDelegate

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if ([PFXKeyboardStateListener sharedInstance].visible) {
        return YES;
    }

    return NO;
}

Oraz PFXKeyboardStateListener.h:

@interface PFXKeyboardStateListener : NSObject
{
    BOOL _isVisible;
}

+ (PFXKeyboardStateListener *)sharedInstance;

@property (nonatomic, readonly, getter=isVisible) BOOL visible;

@end

Oraz PFXKeyboardStateListener.m:

static PFXKeyboardStateListener *sharedInstance;

@implementation PFXKeyboardStateListener

+ (PFXKeyboardStateListener *)sharedInstance
{
    return sharedInstance;
}

+ (void)load
{
    @autoreleasepool {
        sharedInstance = [[self alloc] init];
    }
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (BOOL)isVisible
{
    return _isVisible;
}

- (void)didShow
{
    _isVisible = YES;
}

- (void)didHide
{
    _isVisible = NO;
}

- (id)init
{
    if ((self = [super init])) {
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
        [center addObserver:self selector:@selector(didShow) name:UIKeyboardDidShowNotification object:nil];
        [center addObserver:self selector:@selector(didHide) name:UIKeyboardWillHideNotification object:nil];
    }
    return self;
}

@end

Może chcesz zaktualizować wzór singletonu dla nasłuchującego na klawiaturze, jeszcze go nie dostałem. Mam nadzieję, że to zadziała dla wszystkich innych, a także dla mnie. ^^

bgfriend0
źródło
0

Zaimplementuj tę metodę dla delegata UIGestureRecognizer:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch
{
  UIView *superview = touch.view;
  do {
    superview = superview.superview;
    if ([superview isKindOfClass:[UITableViewCell class]])
      return NO;
  } while (superview && ![superview isKindOfClass:[UITableView class]]);

  return superview != nil;
}
cxa
źródło
0

Szybko możesz użyć tego w środku

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    if CheckTheTime() == true {
        // do something 
    }else{
    }
}

func CheckTheTime() -> Bool{
    return true
}
Abdulrahman Masoud
źródło
0

Moja sprawa była inna, w tym uisearchbar i uitableview na self.view. Chciałem zamknąć klawiaturę uisearchbar, dotykając widoku.

var tapGestureRecognizer:UITapGestureRecognizer?

override func viewWillAppear(animated: Bool) {
    tapGestureRecognizer = UITapGestureRecognizer(target: self, action:Selector("handleTap:"))
}

W przypadku metod delegowania UISearchBar:

func searchBarShouldBeginEditing(searchBar: UISearchBar) -> Bool {
    view.addGestureRecognizer(tapGestureRecognizer!)
    return true
}
func searchBarShouldEndEditing(searchBar: UISearchBar) -> Bool {
    view.removeGestureRecognizer(tapGestureRecognizer!)
    return true
}

Gdy użytkownik dotknie self.view:

func handleTap(recognizer: UITapGestureRecognizer) {
    sampleSearchBar.endEditing(true)
    sampleSearchBar.resignFirstResponder()
}
AG
źródło
0

Swift 5, maj 2020 r.

Mam textField i tableView, które stają się widoczne po wprowadzeniu tekstu.

Stan początkowy Więc chcę 2 różne zdarzenia, kiedy dotknę tableViewCell lub coś innego.

Wyświetlane są klawiatura i widok tabeli

Najpierw dodajemy tapGestureRecognizer.

tap = UITapGestureRecognizer(target: self, action: #selector(viewTapped))
tap.delegate = self
view.addGestureRecognizer(tap)

@objc func viewTapped() {
        view.endEditing(true)
}

Następnie dodajemy następujące sprawdzenie do UIGestureRecognizerDelegate:

extension StadtViewController: UIGestureRecognizerDelegate {

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if touch.view?.isDescendant(of: self.tableView) == true {
        return false
    } else {
        view.endEditing(true)
        return true
    }
}

}

Jeśli najpierw chcę ukryć klawiaturę, tableView pozostaje widoczny i reaguje na dotyk.

wprowadź opis zdjęcia tutaj

Turcja-człowiek
źródło
-1

To jest moje rozwiązanie oparte na powyższych odpowiedziach ... To zadziałało dla mnie ...

//Create tap gesture for menu transparent view
UITapGestureRecognizer *rightTableTransparentViewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(rightTableTransparentViewTapMethod:)];
[rightTableTransparentViewTap setCancelsTouchesInView:NO];
[_rightTableTransparentView addGestureRecognizer:rightTableTransparentViewTap];

- (void)rightTableTransparentViewTapMethod:(UITapGestureRecognizer *)recognizer {
    //Write your code here
}
iOS
źródło
-1

PROBLEM: W moim przypadku problemem było to, że pierwotnie umieściłem przycisk w każdej komórce collectionView i ustawiłem ograniczenia, aby wypełnić komórkę, tak aby po kliknięciu komórki klikał przycisk, jednak funkcja przycisków była pusta, więc nic nie było wydaje się, że się dzieje.

POPRAWKA: Naprawiłem to, usuwając przycisk z komórki widoku kolekcji.

Marcus Levi
źródło