UIGestureRecognizer блокує підпрогляд для обробки сенсорних подій


82

Я намагаюся зрозуміти, як це робиться правильно . Я намагався зобразити ситуацію: введіть тут опис зображення

Я додаю a UITableViewяк підпрогляд UIView. Відповідь UIViewреагує на натискання та pinchGestureRecognizer, але, роблячи це, перегляд таблиці перестає реагувати на ці два жести (він все ще реагує на проведення пальцями).

Я змусив це працювати з наступним кодом, але це, очевидно, не приємне рішення, і я впевнений, що є кращий спосіб. Це поміщено в UIView(суперпогляд):

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

Відповіді:


182

У мене була дуже подібна проблема, і я знайшов своє рішення у цьому SO-питанні . Підсумовуючи, встановіть себе делегатом для свого, UIGestureRecognizerа потім перевірте цільовий вигляд, перш ніж дозволити впізнавачу обробити дотик. Відповідний метод делегування:

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

3
Мені найбільше подобається це рішення, оскільки воно не передбачає возиння з дотиками hitTest:withEvent:або pointInside:withEvent:.
DarkDust

6
Чисте рішення, яке ви можете протестувати, наприклад, return !(touch.view == givenView);якщо ви просто хочете виключити певний вигляд або return !(touch.view.tag == kTagNumReservedForExcludingViews);коли ви хочете зупинити розпізнавач, який обробляє дотик до цілої групи різних підпрограм.
кейт

6
Я б зробив тест на хіт - (BOOL)isDescendantOfView:(UIView *)view. Це також чудово працює - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)eventпри підкласуванні UIGestureRecognizer.
Крістоф

1
@Justin, що, якщо наш розпізнавач жестів належить uiscrollview, і ми не можемо делегувати розпізнавачі жестів?
Gökhan Barış Aker

108

Блокування подій дотику до підпрограм є поведінкою за замовчуванням. Ви можете змінити таку поведінку:

UITapGestureRecognizer *r = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(agentPickerTapped:)];
r.cancelsTouchesInView = NO;
[agentPicker addGestureRecognizer:r];

5
Це призведе до надсилання подій дотиків до підпроектів, але також до розпізнавача жестів. Отже, це запобіжить блокуванню підпроглядів, але розпізнавач жестів все одно буде розпізнаватися в підпрограмах.
Джонатан.

@ Джонатан. Так, я з вами згоден. Те, що я зробив у своєму методі обробки жестів, це перевірити, чи відбувається місце розташування жесту всередині відповідного підпрогляду, і в цьому випадку мені не потрібно виконувати решту коду. Крім того, однією з причин, я вибрав цей спосіб вирішення, є те, що UITapGestureRecognizer не оголошує translationInViewметод. Тож реалізація згаданого вище методу UIGestureRecognizerDelegate призведе лише до збою з помилкою ... unrecognized selector sent to blah. Для того, щоб перевірити, використовувати що - щось на кшталт: CGRectContainsPoint(subview.bounds, [recognizer locationInView:subview]).
MkVal

Відповідно до запитання ОП, цю ​​відповідь слід обрати основною відповіддю.
farzadshbfn

5

Я відображав спадне меню, яке мало свій власний перегляд таблиці. Як результат, touch.viewінколи повертають такі класи, як UITableViewCell. Мені довелося пройти через суперклас (и), щоб переконатися, що це був підклас, який я думав:

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

Цикл while пояснює це
joslinm

5

Спираючись на @Pin Ши Ван відповідь . Ми ігноруємо всі натискання, крім тих, що знаходяться у поданні, що містить розпізнавач жестів. Усі крани пересилаються до ієрархії подання як зазвичай, як ми встановили tapGestureRecognizer.cancelsTouchesInView = false. Ось код у 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)
    }
}

Як порівняти розпізнавач жестів із a UISwipeActionStandardButton? Я пробував з.some(UISwipeActionStandardButton)
Джаліл

4

Однією з можливостей є підклас вашого розпізнавача жестів (якщо ви ще цього не зробили) і перевизначення -touchesBegan:withEvent:таким чином, щоб він визначав, чи починався кожен дотик у виключеному підпрогляді, і викликав -ignoreTouch:forEvent:цей дотик, якщо він був.

Очевидно, вам також потрібно буде додати властивість, щоб відстежувати виключений підпрогляд, або, можливо, краще масив виключених підпроглядів.


Тут є купа коду github.com/…
johndpope

2

Можна обійтися без успадкування будь-якого класу.

Ви можете перевірити gestureRecognizers у селекторі зворотного виклику жесту

якщо view.gestureRecognizers не містить вашого gestureRecognizer, просто ігноруйте його

наприклад

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

перевірте 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
    }
}

Як згадувалося в інших відповідях, якщо ви використовуєте цей метод, переконайтесь, що розпізнавач жестів пересуває кран у вашу ієрархію перегляду за допомогою: singleTapGesture.cancelsTouchesInView = NO;додано до viewDidLoadвище
Нік Егер

1

Я створив підклас UIGestureRecognizer, призначений для блокування всіх розпізнавачів жестів, приєднаних до суперпереглядів певного представлення.

Це частина мого проекту WEPopover. Ви можете знайти його тут .


1

реалізуйте делегат для всіх розпізнавачів parentView і помістіть метод gestureRecognizer у делегат, який відповідає за одночасне спрацьовування розпізнавачів:

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

}

Ви можете використовувати методи відмови, якщо ви хочете, щоб дочірні активатори запускались, але не батьківські розпізнавачі:

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


0

Я також робив поповер, і ось як я це зробив

func didTap(sender: UITapGestureRecognizer) {

    let tapLocation = sender.locationInView(tableView)

    if let _ = tableView.indexPathForRowAtPoint(tapLocation) {
        sender.cancelsTouchesInView = false
    }
    else {
        delegate?.menuDimissed()
    }
}

0

Ви можете вимкнути і ввімкнути .... у моєму коді я зробив щось подібне, оскільки мені потрібно було його вимкнути, коли клавіатура не відображалася, ви можете застосувати це до своєї ситуації:

називати це viewdidload і т.д.:

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

потім створіть два методи:

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

Це робить відключення крана. Мені довелося перетворити tap на змінну екземпляра і випустити його в dealloc.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.