UIGestureRecognizer блокирует подпредставление для обработки событий касания


82

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

Я добавляю UITableViewкак подвид UIView. Объект UIViewреагирует на касание и pinchGestureRecognizer, но при этом tableview перестает реагировать на эти два жеста (он по-прежнему реагирует на смахивания).

Я заставил его работать с помощью следующего кода, но это явно не лучшее решение, и я уверен, что есть способ лучше. Это помещено в 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);когда вы хотите, чтобы ваш распознаватель не обрабатывал касание для целой группы различных подвидов.
cate

6
Я бы сделал тест на попадание с - (BOOL)isDescendantOfView:(UIView *)view. Это также отлично работает - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)eventпри создании подкласса UIGestureRecognizer.
Christoph

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

109

Блокировка событий касания для подпредставлений - поведение по умолчанию. Вы можете изменить это поведение:

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

Согласно вопросу OP, этот ответ следует выбрать в качестве основного ответа.
farzadshbfn 05

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.