UIButton w widoku, który ma UITapGestureRecognizer


184

Mam widok z UITapGestureRecognizer. Więc kiedy stukam w widok, nad tym widokiem pojawia się inny widok. Ten nowy widok ma trzy przyciski. Kiedy teraz naciskam jeden z tych przycisków, nie otrzymuję akcji przycisków, dostaję tylko akcję gestu stuknięcia. Nie mogę już używać tych przycisków. Co mogę zrobić, aby uzyskać dostęp do zdarzeń za pomocą tych przycisków? Dziwne jest to, że przyciski wciąż są podświetlane.

Nie mogę po prostu usunąć UITapGestureRecognizer po otrzymaniu go z kranu. Ponieważ dzięki niemu nowy widok można również usunąć. Oznacza, że ​​chcę takie zachowanie, jak kontrolki wideo na pełnym ekranie .

Odpowiedzi:


256

Możesz ustawić kontroler lub widok (w zależności od tego, który program rozpoznaje gest) jako delegata UITapGestureRecognizer. Następnie w delegacie możesz wdrożyć -gestureRecognizer:shouldReceiveTouch:. W swojej implementacji możesz sprawdzić, czy dotyk należy do nowego widoku podrzędnego, a jeśli tak, poinstruuj rozpoznawanie gestów, aby go zignorowało. Coś w stylu:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    // test if our control subview is on-screen
    if (self.controlSubview.superview != nil) {
        if ([touch.view isDescendantOfView:self.controlSubview]) {
            // we touched our control surface
            return NO; // ignore the touch
        }
    }
    return YES; // handle the touch
}

W moim pliku nagłówkowym miałem widok implementujący UIGestoreRegognizerDelegate, aw moim .mi dodałem twój kod powyżej. Kran nigdy nie wchodzi w tę metodę, idzie prosto do mojego handlera. Jakieś pomysły?
kmehta

1
@kmehta najprawdopodobniej zapomniałeś ustawić właściwość delegowania UIGestureRecognizer.
Do

Czy można uogólnić tę odpowiedź, aby uwzględnić wszystkie przypadki, w których występuje IBAction?
Martin Wickman,

@Martin IBAction kompiluje się w voidtaki sposób, aby nie pozostawiał żadnych informacji w czasie wykonywania w celu użycia do wykrywania. Ale możesz przejść do hierarchii widoków, przetestować UIControli powrócić, NOjeśli znajdziesz jakieś elementy sterujące.
Lily Ballard

dzięki za to, pomaga mi. jeśli przycisk znajduje się w UIImageView, upewnij się, czy userInteractionEnabled z imageView jest ustawiony na YES.
james075

156

Jako kontynuacja odpowiedzi Caseya na odpowiedź Kevina Ballarda:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
        if ([touch.view isKindOfClass:[UIControl class]]) {
            // we touched a button, slider, or other UIControl
            return NO; // ignore the touch
        }
    return YES; // handle the touch
}

Zasadniczo działa to na wszystkie typy elementów sterujących wprowadzanych przez użytkownika, takie jak przyciski, suwaki itp


1
To elastyczne rozwiązanie, któremu może towarzyszyć setCancelsTouchesInView = NObrak wyzwalania gestu stuknięcia podczas interakcji z elementami sterującymi. Można to jednak lepiej napisać:return ![touch.view isKindOfClass:[UIControl class]];
griga13

Rozwiązanie Swift 4+: powrót! (Touch.view to UIControl)
nteissler

103

Znalazłem tę odpowiedź tutaj: link

Możesz także użyć

tapRecognizer.cancelsTouchesInView = NO;

Co uniemożliwia rozpoznanie kranu jako jedynego, który przechwytuje wszystkie krany

AKTUALIZACJA - Michael wspomniał o linku do dokumentacji opisującej tę właściwość: cancelsTouchesInView


8
To powinna być zaakceptowana odpowiedź IMO. To pozwala każdemu podviewowi samodzielnie obsługiwać kran i zapobiega widokowi, do którego jest dołączony tabrecognizer, przed „podniesieniem” kranu. Nie musisz wdrażać delegata, jeśli wszystko, co chcesz zrobić, to umożliwić kliknięcie widoku (aby zrezygnować z pierwszej odpowiedzi / ukryć klawiaturę w polach tekstowych itp.) .. niesamowite!
EeKay,

4
To zdecydowanie powinna być zaakceptowana odpowiedź. Ta odpowiedź skłoniła mnie do prawidłowego przeczytania dokumentacji Apple , co wyjaśnia, że ​​rozpoznawanie gestów uniemożliwi otrzymanie rozpoznanych zdarzeń przez podview, chyba że to zrobisz.
Michael van der Westhuizen,

1
Kiedyś to działało, ale teraz nie działa dla mnie .. może dlatego, że mam przycisk scrollView pod przyciskiem? Musiałem wdrożyć delegata, jak w powyższych odpowiedziach.
xissburg,

7
Po drobnych eksperymentach zauważyłem, że zadziała to tylko wtedy, gdy widok zawierający rozpoznawanie gestów jest rodzeństwem UIControl, a nie będzie, jeśli widok jest rodzicem UIControl.
xissburg,

6
Naprawdę nie działa zgodnie z przeznaczeniem ... TAK, zapobiega rozpoznawaniu tapów, aby zjeść wydarzenie na UIControl. Ale nadal działa akcja rozpoznająca, która jest uruchamiana jednocześnie 2.
Tek Yin

71

W odpowiedzi na odpowiedź Kevina Ballarda miałem ten sam problem i ostatecznie wykorzystałem ten kod:

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

Ma ten sam efekt, ale zadziała na każdym UIButtonie na dowolnej głębokości widoku (mój UIButton miał kilka widoków głębokich, a delegat UIGestureRecognizer nie miał do niego odniesienia).


6
Nie chcę być tu kutasem, ale to naprawdę TAK i NIE. Proszę stosować konwencje kakao. TRUE / FALSE / NULL jest dla warstwy CoreFoundation.
steipete,

z tego co przeczytałem, TAK i NIE zostały stworzone dla czytelności. czytelność jest subiektywna. wynika z konwencji nazewnictwa metod i właściwości, o które nie wszyscy się przejmują. jeśli chcesz ściśle przestrzegać konwencji nazewnictwa, to tak, użyj TAK i NIE dla dowolnego NSWokolwiek. powiem jednak, że jeśli sondujecie programistów, otrzymacie mieszane opinie, które z nich są bardziej czytelne [przycisk setHidden: YES]; -lub- [przycisk setHidden: TRUE];
Pimp Juice McJones,

1
Myślę, że próbuję powiedzieć, że jeśli konwencje nazewnictwa są czytelne, to trudno jest zaprzeczyć, że w niektórych przypadkach jest to subiektywne. jeśli jesteś purystą, nie zrozumiesz tego stwierdzenia.
Pimp Juice McJones,

2
Może się to wydawać subiektywne, ale po pewnym czasie programowania Cocoa naprawdę wchodzisz w bardziej semantycznie dokładny przepływ kodu ... „PRAWDA” po latach Objective-C wydaje się naprawdę nie tak, ponieważ nie pasuje do „ukrytego” bardzo dobrze.
Kendall Helmstetter Gelner

Od kiedy możesz / czy przekazujesz gest dotknięcia przycisku UIButton jako zdarzenia Touch Up Inside UITouch (ten ostatni to zdarzenie generowane przez dotknięcie przycisku UIButton)? Wydaje się powielanie i niepoprawne tworzenie rozpoznawania gestów dotknięcia dla przycisku, który ma już 15 zdarzeń dotykowych powiązanych z gestem dotknięcia. Chciałbym zobaczyć kod, a nie domysły.
James Bush

10

W systemie iOS 6.0 i nowszych domyślne czynności kontrolne zapobiegają zachodzeniu na siebie rozpoznawania gestów. Na przykład domyślną czynnością dla przycisku jest pojedyncze dotknięcie. Jeśli do widoku rodzica przycisku jest podłączone rozpoznawanie gestów jednego dotknięcia, a użytkownik stuknie przycisk, wówczas metoda działania przycisku odbiera zdarzenie dotykowe zamiast rozpoznawania gestu. Dotyczy to tylko rozpoznawania gestów, które nakładają się na domyślną akcję dla kontrolki, która obejmuje: .....

Z dokumentu API Apple


8

Te odpowiedzi były niepełne. Musiałem przeczytać wiele postów, jak korzystać z tej operacji boolowskiej.

Dodaj do pliku * .h

@interface v1ViewController : UIViewController <UIGestureRecognizerDelegate>

Dodaj do pliku * .m

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

    NSLog(@"went here ...");

    if ([touch.view isKindOfClass:[UIControl class]])
    {
        // we touched a button, slider, or other UIControl
        return NO; // ignore the touch
    }
    return YES; // handle the touch
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.



    //tap gestrure
    UITapGestureRecognizer *tapGestRecog = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenTappedOnce)];
    [tapGestRecog setNumberOfTapsRequired:1];
    [self.view addGestureRecognizer:tapGestRecog];


// This line is very important. if You don't add it then your boolean operation will never get called
tapGestRecog.delegate = self;

}


-(IBAction) screenTappedOnce
{
    NSLog(@"screenTappedOnce ...");

}

7

Znalazłem inny sposób na zrobienie tego stąd . Wykrywa dotyk, czy wewnątrz każdego przycisku, czy nie.

(1) pointInside: withEvent: (2) locationInView:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
       shouldReceiveTouch:(UITouch *)touch {
    // Don't recognize taps in the buttons
    return (![self.button1 pointInside:[touch locationInView:self.button1] withEvent:nil] &&
            ![self.button2 pointInside:[touch locationInView:self.button2] withEvent:nil] &&
            ![self.button3 pointInside:[touch locationInView:self.button3] withEvent:nil]);
}

Wypróbowałem wszystkie inne rozwiązania, ale tylko dla mnie działało podejście -pointInside: withEvent:
Kenny Wyland

3

Oto szybka wersja odpowiedzi Lily Ballard, która zadziałała dla mnie:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if (scrollView.superview != nil) {
        if ((touch.view?.isDescendantOfView(scrollView)) != nil) { return false }
    }
    return true
}

3

Szybki 5

Przycisk podglądu z tapgesture

 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if let _ = touch.view as? UIButton { return false }
    return true
}

W moim przypadku wdrożenie hitTest działało dla mnie. Miałem widok kolekcji z przyciskiem

Ta metoda przegląda hierarchię widoków, wywołując point(inside:with:)metodę każdego podviewu w celu ustalenia, który podview powinien otrzymać zdarzenie dotykowe. Jeśli point(inside:with:)zwróci wartość true, hierarchia widoku podrzędnego jest podobnie przechodzona, dopóki nie zostanie znaleziony widok z przodu zawierający określony punkt.

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard isUserInteractionEnabled else { return nil }

    guard !isHidden else { return nil }

    guard alpha >= 0.01 else { return nil }

    guard self.point(inside: point, with: event) else { return nil }

    for eachImageCell in collectionView.visibleCells {
        for eachImageButton in eachImageCell.subviews {
            if let crossButton = eachImageButton as? UIButton {
                if crossButton.point(inside: convert(point, to: crossButton), with: event) {
                    return crossButton
                }
            }
        }
    }
    return super.hitTest(point, with: event)
}

1

Możesz zatrzymać UITapGestureRecognizer przed anulowaniem innych zdarzeń (takich jak dotknięcie przycisku), ustawiając następującą wartość logiczną:

    [tapRecognizer setCancelsTouchesInView:NO];

1

Jeśli twój scenariusz jest taki:

Masz prosty widok i niektóre przyciski UIB, elementy sterujące UITextField dodane jako widoki podrzędne do tego widoku. Teraz chcesz zamknąć klawiaturę po dotknięciu dowolnego miejsca w widoku, z wyjątkiem elementów sterujących (dodanych widoków podrzędnych)

Zatem rozwiązaniem jest:

Dodaj następującą metodę do XYZViewController.m (która ma twój widok)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.view endEditing:YES];
}

Najpierw próbowałem z odpowiedzią @cdasher , ale w moim przypadku nawet po kliknięciu przycisku otrzymuję touch.view jako mój „widok” w ViewController, ale nie mój „UIButton”. Czy ktoś może powiedzieć, dlaczego właściwość widoku dotyku nie zwraca pierwotnego widoku, w którym nastąpiło dotknięcie
NaveenRaghuveer

1
Nie będzie działać, jeśli masz widok przewijania lub inny widok, który przejmuje zdarzenia dotykowe.
lkraider,

1

Dostajesz optymalizację odpowiedzi cdasher

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch 
{
    return ![touch.view isKindOfClass:[UIControl class]];
}
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.