Кто первым получит событие, если на View установлен Gesture Recognizer?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Порядок обработки событий с UIGestureRecognizer
При наложении Gesture Recognizer на UIView возникает комплексное взаимодействие между системой обработки событий (event delivery chain), самим распознавателем жестов и методами UIResponder. Чтобы понять, кто первым получит событие, нужно рассмотреть весь путь UITouch от окна до конечного view.
Иерархия доставки событий (Event Delivery Chain)
- UIApplication → UIWindow → Корневой UIViewController.view → ... → Целевой UIView (самый глубокий в иерархии).
- Система использует метод
hitTest(_:with:)для определения первого респондера (first responder) – того view, которое станет вершиной цепочки ответчиков (responder chain) для данного касания. - Касания (UITouch) первоначально доставляются именно этому view и его методам UIResponder (таким как
touchesBegan(_:with:)).
Однако, UIGestureRecognizer вмешивается в этот процесс.
Ключевой принцип: UIGestureRecognizer как перехватчик
По умолчанию, распознаватель жестов получает событие РАНЬЖЕ, чем методы touchesBegan у UIView. Это фундаментальное правило.
class CustomView: UIView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Этот метод будет вызван ПОСЛЕ того,
// как UIGestureRecognizer обработает касание.
print("CustomView.touchesBegan")
super.touchesBegan(touches, with: event)
}
}
// Где-то в коде ViewController:
let customView = CustomView()
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
customView.addGestureRecognizer(tapRecognizer)
@objc func handleTap() {
print("GestureRecognizer handler triggered")
// Этот хэндлер сработает ПЕРВЫМ при успешном распознавании.
}
В консоли при тапе вы скорее всего увидите:
GestureRecognizer handler triggered
CustomView.touchesBegan
Детальный порядок и состояния UIGestureRecognizer
- Фаза
Possible(Возможно): Как только система определяет первый респондер черезhitTest, все прикрепленные к этому view и его супервью UIGestureRecognizer получают поток касаний. Они начинают анализ в состоянии.possible. - Анализ жеста: Распознаватель (например,
UITapGestureRecognizer) отслеживает последовательностьtouchesBegan->touchesMoved->touchesEnded, пытаясь сопоставить её со своим шаблоном. - Приоритет перед
UIResponder: Пока распознаватель находится в состоянии.possible, система задерживает (delays) вызов методовtouchesBegan(_:with:)у соответствующего UIView. Это называется delayed touches. - Исход анализа:
* **Если жест распознан УСПЕШНО**: Распознаватель переходит в состояние `.ended` (для дискретных жестов, как тап) или `.began` (для непрерывных, как pan). Вызывается его **target-action** (например, `handleTap`). **Только после этого**, если жест не был отменен, задержанные методы `touchesBegan` могут быть доставлены view (но часто они просто отменяются).
* **Если жест НЕ распознан**: Распознаватель переходит в состояние `.failed`. Тогда система немедленно доставляет view все накопленные задержанные события, вызывая стандартные методы `touchesBegan`, `touchesMoved` и т.д.
Управление порядком: свойства cancelsTouchesInView и delaysTouchesBegan
Поведение по умолчанию можно тонко настраивать:
-
cancelsTouchesInView(по умолчаниюtrue): Если жест распознан успешно, все последующие события касания для этого view отменяются (вызываетсяtouchesCancelled(_:with:)). Это предотвращает "двойную" обработку и жестом, и view.tapRecognizer.cancelsTouchesInView = false // Теперь при успешном тапе вызовутся: // 1. handleTap (распознаватель) // 2. touchesBegan, touchesEnded у view (отмена не произойдет) -
delaysTouchesBegan(по умолчаниюtrue): Как описано выше, задерживает вызовtouchesBeganу view до момента, пока распознаватель не перейдет в состояние.failed. Если установить вfalse,touchesBeganвызовется у view почти одновременно с началом анализа жестом, что может нарушить логику.tapRecognizer.delaysTouchesBegan = false // touchesBegan у view вызовется сразу, параллельно с анализом жеста.
Специальный случай: Одновременные жесты (Simultaneous Recognition)
Если на view несколько распознавателей, они по умолчанию работают по принципу исключительности (жест "поглощается" первым успешно распознавшим). Чтобы разрешить одновременное распознавание, используется делегат UIGestureRecognizerDelegate и метод:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true // Оба жеста смогут работать одновременно
}
В этом случае оба распознавателя получают события параллельно и оба могут перейти в конечное состояние, но они все равно имеют приоритет перед native touches view.
Итог: кто же первый?
Абсолютно первыми поток касаний получают UIGestureRecognizer, прикрепленные к view и его супервьюм. Методы touchesBegan у UIView вызываются с задержкой и только в том случае, если:
- Все участвующие распознаватели жестов перешли в состояние
.failed(не узнали жест), ИЛИ - У распознавателя явно отключены свойства
delaysTouchesBeganи/илиcancelsTouchesInView.
Понимание этого порядка критично для корректной обработки сложных интеракций, создания кастомных жестов и избегания конфликтов между встроенными и пользовательскими обработчиками касаний.