В iOS, если на UIView добавлен UIGestureRecognizer, кто обрабатывает касание первым?

Ответ

Первым событие касания получает UIGesture Recognizer, а не методы touchesBegan(_:with:) самого UIView. Это фундаментальный принцип архитектуры обработки событий в UIKit.

Порядок обработки событий

  1. Hit-Testing: Система определяет UIView, которая получила касание, с помощью hitTest(_:with:).
  2. Gesture Recognizer Priority: Для этой view система сначала предлагает событие всем attached UIGestureRecognizer.
  3. Распознавание: Если какой-либо gesture recognizer распознает жест (переходит в состояние .recognized), он может "захватить" поток событий.
  4. Delivery to View: События передаются методам touchesBegan view только если:
    • Все gesture recognizer не смогли распознать жест (состояние .failed).
    • Распознавателю установлено свойство cancelsTouchesInView = false.

Практический пример и контроль

class CustomView: UIView {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Этот код выполнится ПОСЛЕ gesture recognizer
        print("View touchesBegan")
    }
}

let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
// tapRecognizer получит и обработает событие ДО touchesBegan
myView.addGestureRecognizer(tapRecognizer)

// Для тонкого контроля можно использовать делегат:
tapRecognizer.delegate = self
// Метод делегата позволяет решить, должен ли recognizer получать события
// одновременно с view или блокировать их.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true // Разрешить одновременную работу нескольких распознавателей
}

Ключевые свойства:

  • cancelsTouchesInView (по умолчанию true): При распознавании отменяет доставку событий к view.
  • delaysTouchesBegan (по умолчанию false): Задерживает вызов touchesBegan у view до тех пор, пока recognizer не перейдет в состояние .failed.

Вывод: UIGestureRecognizer является приоритетным обработчиком в chain of responsibility для событий касания.

Ответ 18+ 🔞

Смотри, вот тебе на пальцах, как эта ебля с касаниями в UIKit работает, а то многие думают, что они в touchesBegan короли, а на деле — так, подстилка для настоящих боссов.

Кто главный на районе? Первым на любое касание налетает не твой UIView с его touchesBegan(_:with:), а UIGestureRecognizer. Это, блядь, железное правило, архитектура такая, ёпта. Твоя вьюшка — это как честный работяга, а распознаватель жестов — это бригадир с бейсбольной битой. Сначала все события идут через него.

Как это всё происходит, по шагам:

  1. Hit-Testing (Кто под прицелом): Система тыкает пальцем в экран и спрашивает: «Эй, hitTest(_:with:), кто тут самый верхний и прозрачный?» Находит твою вьюшку.
  2. Приоритет Ганста-Распознавателей: Система смотрит на эту вьюшку и говорит: «О, тут прикреплены какие-то UIGestureRecognizer. Ребята, вам что-нибудь надо?» И суёт событие сначала им.
  3. Распознавание (Момент истины): Если какой-нибудь распознаватель хватается за событие и орет «Это моё! Я узнал тап/свайп/пинч!» (переходит в состояние .recognized), то он может просто увести весь поток событий себе, как последняя мартышлюшка банан.
  4. Доставка бедной вьюшке: События доползут до методов touchesBegan твоей вью только в двух случаях:
    • Все распознаватели посмотрели на касание, пожали плечами и сказали «Не, это не наш формат» (состояние .failed).
    • Или если ты конкретному распознавателю сказал: «Слушай, не будь мудаком, дай и вьюшке поучаствовать» — выставил cancelsTouchesInView = false.

Пример из жизни, чтобы не быть мудаком:

class CustomView: UIView {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Этот код выполнится, блядь, ПОСЛЕ того, как gesture recognizer отъестся!
        print("View touchesBegan — если, конечно, до меня доживут")
    }
}

let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
// tapRecognizer сожрёт событие ДО touchesBegan, как голодный хуй с горы
myView.addGestureRecognizer(tapRecognizer)

// Но если ты хочешь тонко управлять этим цирком, есть делегат:
tapRecognizer.delegate = self

// Вот этот метод — как договор между бандами. Разрешаем работать одновременно.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true // Да похуй, пусть все работают, лишь бы не дрались.
}

Главные рычаги давления (свойства):

  • cancelsTouchesInView (по дефолту true): Если распознаватель сработал, он может отменить доставку событий вьюшке. Полный пиздец для touchesBegan.
  • delaysTouchesBegan (по дефолту false): Может специально задерживать вызов touchesBegan у вью, пока сам не решит — получилось у него распознать жест или нет.

Вывод, ёпта: UIGestureRecognizer — это главный барин в этой иерархии, приоритетный обработчик в цепочке ответственности. Твоя вьюшка со своими touchesBegan — это так, запасной аэродром, на который садятся, только если все жесты промазали. Держи это в голове, и волнение ебать исчезнет.