← Назад к вопросам

Кто первым получит событие, если на View установлен Gesture Recognizer?

1.7 Middle🔥 111 комментариев
#UIKit и верстка

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

При наложении Gesture Recognizer на UIView возникает комплексное взаимодействие между системой обработки событий (event delivery chain), самим распознавателем жестов и методами UIResponder. Чтобы понять, кто первым получит событие, нужно рассмотреть весь путь UITouch от окна до конечного view.

Иерархия доставки событий (Event Delivery Chain)

  1. UIApplicationUIWindowКорневой UIViewController.view → ... → Целевой UIView (самый глубокий в иерархии).
  2. Система использует метод hitTest(_:with:) для определения первого респондера (first responder) – того view, которое станет вершиной цепочки ответчиков (responder chain) для данного касания.
  3. Касания (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

  1. Фаза Possible (Возможно): Как только система определяет первый респондер через hitTest, все прикрепленные к этому view и его супервью UIGestureRecognizer получают поток касаний. Они начинают анализ в состоянии .possible.
  2. Анализ жеста: Распознаватель (например, UITapGestureRecognizer) отслеживает последовательность touchesBegan -> touchesMoved -> touchesEnded, пытаясь сопоставить её со своим шаблоном.
  3. Приоритет перед UIResponder: Пока распознаватель находится в состоянии .possible, система задерживает (delays) вызов методов touchesBegan(_:with:) у соответствующего UIView. Это называется delayed touches.
  4. Исход анализа:
    *   **Если жест распознан УСПЕШНО**: Распознаватель переходит в состояние `.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 вызываются с задержкой и только в том случае, если:

  1. Все участвующие распознаватели жестов перешли в состояние .failed (не узнали жест), ИЛИ
  2. У распознавателя явно отключены свойства delaysTouchesBegan и/или cancelsTouchesInView.

Понимание этого порядка критично для корректной обработки сложных интеракций, создания кастомных жестов и избегания конфликтов между встроенными и пользовательскими обработчиками касаний.