UITapGestureRecognizer нажимает на self.view, но игнорирует subviews

97

Мне нужно реализовать функцию, которая будет вызывать некоторый код, когда я дважды нажимаю на self.view (вид UIViewCotroller). Но проблема в том, что у меня есть другой объект пользовательского интерфейса в этом представлении, и я не хочу прикреплять какой-либо объект распознавания ко всем из них. Я нашел этот метод ниже, как сделать жест на моем представлении, и я знаю, как он работает. Прямо сейчас я столкнулся с препятствием, какой способ выбрать для создания этого распознавателя, игнорирующего subview. Любые идеи? Спасибо.

UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
[doubleTap setNumberOfTapsRequired:2];
[self.view addGestureRecognizer:doubleTap];
Матросов Александр
источник
1
Я не уверен, но пробовали ли вы установить для cancelsTouchesInView значение NO на распознавателе? поэтому [doubleTap setCancelsTouchesInView: NO];
JDx

Ответы:

147

Вы должны принять UIGestureRecognizerDelegateпротокол внутри selfобъекта и вызвать приведенный ниже метод для проверки представления. Внутри этого метода проверьте свое представление touch.viewи верните соответствующий тип bool (Да / Нет). Что-то вроде этого:

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

Изменить: пожалуйста, также проверьте ответ @Ian!

Swift 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
}
Рави
источник
еще один вопрос. У меня есть несколько кнопок, которые должны работать. как я могу установить для них какой-то приоритет?
Матросов Александр
если у ваших кнопок есть свои собственные средства распознавания жестов (вероятно), покопайтесь в их внутренностях, возьмите их и прочтите документацию, -[UIGestureRecognizer requireGestureRecognizerToFail:]но это может работать без их
модификации
искал это часами! Благодарность! ;)
MasterRazer
Если ваш ifтест не прошел, ваша реализация не вернет BOOL; для правильного стиля верните YES после блока if (используя { }) или в elseветви. Тем не менее, спасибо, я сэкономил немного времени на чтении.
RobP
2
Представление является потомком самого себя, поэтому это не работает ... (общий подход в порядке, см. Другой ответ)
Ixx
109

Другой подход состоит в том, чтобы просто сравнить, является ли представление касания представлением жестов, потому что потомки не передают условие. Хороший простой однострочник:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view == gestureRecognizer.view
}
Ян
источник
2
Для меня это работает лучше, чем принятый ответ. Намного более лаконично.
Суман Рой
1
@Ian этот метод не вызывается при нажатии на представление.
Прабурадж
1
Отличный лаконичный ответ!
scurioni
Принятый ответ даже не сработал, но для меня это сработало безупречно.
Шалин Шах
@Prabu Propably потому , что делегат жест распознаватель должен быть установлен до
кубба
23

И для варианта Swift:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if touch.view.isDescendantOfView(yourSubView){
        return false
    }
    return true
}

Полезно знать, isDescendantOfViewвозвращает Booleanзначение, указывающее, является ли получатель частью данного представления или идентичным этому представлению.

Антуан
источник
1
Не забудьте установить UIGestureRecognizerDelegate в своем классе!
Fox5150 05
4
Вы не можете просто вернуть это вместо этого оператора if? return! touch.view.isDescendant (of: gestureRecognizer.view) Также новый синтаксис в swift 3 ^^
EdwardSanchez
13

В Swift 5 и iOS 12 UIGestureRecognizerDelegateесть метод, называемый gestureRecognizer(_:shouldReceive:). gestureRecognizer(_:shouldReceive:)имеет следующее объявление:

Спросите делегата, должен ли распознаватель жестов получать объект, представляющий прикосновение.

optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool

Полный код ниже показывает возможную реализацию для gestureRecognizer(_:shouldReceive:). С помощью этого кода нажатие на подвид ViewControllerпредставления (включая imageView) не запускает printHello(_:)метод.

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")
    }

}

Альтернативной реализацией gestureRecognizer(_:shouldReceive:)может быть:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return gestureRecognizer.view === touch.view
}

Однако обратите внимание, что этот альтернативный код не проверяет, touch.viewявляется ли подпредставление gestureRecognizer.view.

Иману Пети
источник
10

Полное быстрое решение (делегат должен быть реализован и установлен для распознавателя (ов)):

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
        }
    }
}
Арру
источник
6

Вариант с использованием CGPoint, которого вы касаетесь (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
источник
4

Очистить быстрый путь

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view == self.view
}
Муса Алматри
источник
Никогда не вызывается в iOS 13, по крайней мере, с помощью swipeGestureRecognizer.
Chewie The Chorkie
Чем это отличается от ответа Яна?
sweetfa
3

Обратите внимание, что API gestureRecognizer изменился на:

gestureRecognizer (_: shouldReceive :)

Обратите особое внимание на индикатор подчеркивания (пропуска) для внешней метки первого параметра.

Используя многие из приведенных выше примеров, я не получал событие. Ниже приведен пример, который работает для текущих версий 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
}
Рико
источник
2

Плюс к вышеперечисленным решениям, не забудьте проверить User Interaction Enabledсвой подвид.

введите описание изображения здесь

MrDEV
источник
2

Пришлось запретить жест на детском просмотре. Единственное, что сработало, - это разрешить и сохранить первое представление и запретить жест во всех следующих представлениях:

   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
     }
Шэй Морено
источник
1

Swift 4:

touch.view теперь является необязательным, поэтому на основе ответа @Antoine:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if let touchedView = touch.view, touchedView.isDescendant(of: deductibleBackgroundView) {
        return false
    }
    return true
}
Джонатан Кабрера
источник
0

Если вы не хотите , чтобы ваша «дважды нажмите распознавань» в конфликт с вашими кнопками и / или другими элементами управления, вы можете установить , selfкак UIGestureRecognizerDelegateи реализацию:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
{
    return !(touch.view is UIControl)
}
PDK
источник