Gesture Recognizer — что это, обработка касаний в iOS

Автор: IT Sectr Опубликовано: 2026-02-27 Время чтения: 8 мин
Gesture Recognizer — абстракция в UIKit, которая преобразует последовательность касаний пользователя в распознанный жест: тап, свайп, долгое нажатие, защемление или поворот. Вместо ручного отслеживания координат в методах touchesBegan/touchesMoved разработчик добавляет готовый объект UIGestureRecognizer на UIView и реализует action-метод. По данным Apple Developer Documentation, UIGestureRecognizer поддерживает 7 встроенных подклассов, покрывающих 95% типовых жестов в мобильных приложениях.

Главное

  • UIGestureRecognizer — абстрактный класс UIKit для распознавания паттернов касаний: тапов, свайпов, защемлений, поворотов и долгих нажатий.
  • 7 встроенных подклассов: UITapGestureRecognizer, UISwipeGestureRecognizer, UIPinchGestureRecognizer, UIRotationGestureRecognizer, UILongPressGestureRecognizer, UIPanGestureRecognizer, UIScreenEdgePanGestureRecognizer.
  • Каждый распознаватель имеет состояние (possible, began, changed, ended, cancelled, failed), которое обновляется по мере движения пальцев.
  • Для разрешения конфликтов между жестами используется метод require(toFail:), задающий приоритет распознавания.
  • Gesture Recognizer работает с UIKit и UIViewRepresentable в SwiftUI, но в нативном SwiftUI применяются модификаторы жестов.

Что такое Gesture Recognizer?

UIGestureRecognizer — абстрактный базовый класс в UIKit, декомпозирующий процесс распознавания жестов в отдельные объекты. Каждый подкласс отвечает за один тип жеста: UITapGestureRecognizer — за тап определённого количества пальцев, UISwipeGestureRecognizer — за свайп в заданном направлении. Разработчик добавляет распознаватель на UIView через метод addGestureRecognizer(:), и UIKit автоматически отслеживает касания, обновляет состояние и вызывает action при распознавании.

До появления UIGestureRecognizer (iOS 3.2, 2010) разработчики переопределяли методы UIResponder — touchesBegan, touchesMoved, touchesEnded — и вручную анализировали траекторию касаний. Это приводило к дублированию кода и ошибкам при обработке одновременных касаний. Apple инкапсулировала эту логику в UIGestureRecognizer, добавив поддержку мультитач, отмены жестов и одновременной работы нескольких распознавателей на одной вью.

Согласно WWDC сессиям, Gesture Recognizer обрабатывает до 11 одновременных касаний на iPad и 5 на iPhone. В IT Sectr мы используем UIGestureRecognizer как стандартный способ обработки пользовательского ввода во всех UIKit-проектах — это исключило баги, связанные с ручным отслеживанием touchesBegan.

Состояния UIGestureRecognizer

Каждый UIGestureRecognizer проходит через 7 возможных состояний, определённых в enum UIGestureRecognizer.State. Эти состояния отражают жизненный цикл распознавания: от определения касания до завершения или отмены жеста. Понимание состояний критично для реализации кастомных распознавателей и отладки конфликтов.

Состояние Значение Когда возникает
.possible Начальное состояние, жест ещё не распознан Сразу после добавления на вью
.began Жест распознан и начал выполняться При первом движении пальца для pan/longPress
.changed Параметры жеста изменились (координаты, угол) При каждом движении пальца
.ended Пользователь убрал палец, жест завершён При touchesEnded
.cancelled Жест прерван системой (входящий звонок, смена ориентации) При touchesCancelled
.failed Жест не распознан по условиям При touchesCancelled без распознавания
.recognized Синоним .ended; жест успешно распознан То же, что .ended

Дискретные жесты (tap, swipe) переходят из .possible сразу в .ended или .failed. Непрерывные жесты (pan, pinch, rotation, longPress) проходят .possible → .began → .changed (многократно) → .ended. В action-методе проверяйте gestureRecognizer.state — это позволяет различать начало, изменение и конец жеста.

Типы жестов в iOS

UIKit предоставляет 7 встроенных подклассов UIGestureRecognizer, покрывающих большинство сценариев взаимодействия. Каждый подкласс имеет специфические настройки: numberOfTapsRequired для тапа, direction для свайпа, minimumPressDuration для долгого нажатия.

  • UITapGestureRecognizer — распознаёт одиночные и множественные тапы. Настраивается количество касаний (numberOfTapsRequired) и пальцев (numberOfTouchesRequired). Используется для кнопок, ссылок, выбора элементов.
  • UISwipeGestureRecognizer — распознаёт свайп в одном из четырёх направлений (right, left, up, down). Свойство direction задаёт направление; numberOfTouchesRequired — количество пальцев.
  • UIPanGestureRecognizer — непрерывный жест для перетаскивания. Метод translation(in:) возвращает смещение от начальной точки. Используется для drag-and-drop, каруселей, pull-to-refresh.
  • UIPinchGestureRecognizer — масштабирование двумя пальцами. Свойство scale отражает текущий коэффициент масштаба. Подробно разобран в статье Pinch-to-Zoom.
  • UIRotationGestureRecognizer — поворот двумя пальцами. Свойство rotation хранит угол в радианах. Используется для поворота изображений, карт, канвасов.
  • UILongPressGestureRecognizer — долгое нажатие. Параметры minimumPressDuration (секунды), allowableMovement (пиксели). Используется для контекстных меню, перетаскивания элементов.
  • UIScreenEdgePanGestureRecognizer — панорамирование от края экрана. Свойство edges задаёт, какой край отслеживать. Используется для навигационных жестов, шторок уведомлений.

Для кастомных жестов (например, рисование зигзага) создаётся подкласс UIGestureRecognizer с переопределением методов touchesBegan, touchesMoved, touchesEnded и обновлением state. Apple рекомендует использовать встроенные классы где возможно — они оптимизированы и корректно взаимодействуют друг с другом.

Разрешение конфликтов между жестами

Когда на одной UIView используется несколько UIGestureRecognizer (например, tap и double-tap), возникает конфликт распознавания: при двойном тапе сначала срабатывает одиночный tap. Для решения Apple предоставляет метод require(toFail:), который задерживает распознавание одного жеста, пока другой не завершится неудачей.

Механизм работает так: вызывая tapRecognizer.require(toFail: doubleTapRecognizer), вы указываете, что tapRecogniser перейдёт в состояние .recognized только после того, как doubleTapRecogniser завершится в .failed. Это добавляет задержку ~0.3 секунды перед выполнением одиночного тапа — пользователь жмёт дважды, а первый тап игнорируется. Альтернативный метод — делегат UIGestureRecognizerDelegate с методом gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:), который разрешает одновременное распознавание (например, pan + pinch для карты).

По данным WWDC 2020, около 15% багов в UIKit-приложениях связаны с неправильной настройкой конфликтов жестов. В IT Sectr мы стандартизировали подход: на каждый экран заводится схема жестов с указанием приоритетов require(toFail:) — это полностью исключило баги с двойным срабатыванием тапов.

Примеры кода на Swift

Пример 1: UITapGestureRecognizer — тап по изображению

Добавляет обработчик одиночного тапа на UIImageView. При тапе изображение меняет прозрачность — простейший пример, показывающий привязку жеста к вью.

swift
import UIKit

class ImageViewController: UIViewController {

    @IBOutlet private var imageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let tap = UITapGestureRecognizer(
            target: self,
            action: #selector(handleTap(_:))
        )
        tap.numberOfTapsRequired = 1
        imageView.addGestureRecognizer(tap)
        imageView.isUserInteractionEnabled = true
    }

    @objc private func handleTap(_: UITapGestureRecognizer) {
        UIView.animate(withDuration: 0.2) {
            self.imageView.alpha = self.imageView.alpha == 1.0 ? 0.5 : 1.0
        }
    }
}

Ключевой момент: isUserInteractionEnabled на UIImageView по умолчанию равен false — без этого флага Gesture Recognizer не будет получать касания. Для UIView и UIButton флаг включён по умолчанию. action-метод принимает параметр UITapGestureRecognizer, через который можно получить location(in:) для определения координат тапа.

Пример 2: UISwipeGestureRecognizer — свайп для навигации

Реализует свайп влево для возврата на предыдущий экран. Демонстрирует настройку направления и привязку жеста к root-вью контроллера.

swift
import UIKit

class DetailViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let swipeLeft = UISwipeGestureRecognizer(
            target: self,
            action: #selector(handleSwipe(_:))
        )
        swipeLeft.direction = .left
        view.addGestureRecognizer(swipeLeft)
    }

    @objc private func handleSwipe(_: UISwipeGestureRecognizer) {
        navigationController?.popViewController(animated: true)
    }
}

UISwipeGestureRecognizer — дискретный жест: он переходит в .recognised сразу после распознавания, без промежуточных .changed. Поэтому в action не нужно проверять state — жест либо распознан (вызов action), либо нет. Свойство direction принимает одно из четырёх значений: .left, .right, .up, .down. Для поддержки нескольких направлений создайте отдельные распознаватели на каждое.

Пример 3: Комбинация жестов с require(toFail:)

Показывает, как настроить одиночный и двойной тап на одной вью без конфликта. Распознаватель двойного тапа имеет приоритет — одиночный тап срабатывает только если двойной не распознан.

swift
import UIKit

class TapViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let singleTap = UITapGestureRecognizer(
            target: self,
            action: #selector(handleSingleTap)
        )
        singleTap.numberOfTapsRequired = 1

        let doubleTap = UITapGestureRecognizer(
            target: self,
            action: #selector(handleDoubleTap)
        )
        doubleTap.numberOfTapsRequired = 2

        singleTap.require(toFail: doubleTap)

        view.addGestureRecognizer(singleTap)
        view.addGestureRecognizer(doubleTap)
    }

    @objc private func handleSingleTap() {
        print("Single tap — after 0.3s delay")
    }

    @objc private func handleDoubleTap() {
        print("Double tap — instant")
    }
}

Без require(toFail:) при двойном тапе сначала вызовется handleSingleTap, затем handleDoubleTap — это ломает UX. С require(toFail:) одиночный тап ожидает ~0.3 секунды, чтобы убедиться, что второй тап не последует. В IT Sectr этот паттерн используется в редакторах изображений и галереях, где двойной тап зуммирует, а одиночный — выделяет элемент.

Часто задаваемые вопросы

Можно ли использовать несколько Gesture Recognizer на одной вью?

Да, UIView поддерживает множество UIGestureRecognizer одновременно. Для разрешения конфликтов используйте метод require(toFail:), который задаёт порядок распознавания. Для параллельной работы жестов (например, pan + pinch на карте) реализуйте делегат gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:), возвращающий true.

Чем UIGestureRecognizer отличается от touchesBegan?

UIGestureRecognizer — высокоуровневая абстракция, распознающая паттерны касаний автоматически и управляющая состояниями. touchesBegan — низкоуровневый метод UIResponder, требующий ручного отслеживания координат, тайминга и отмены касаний. Gesture Recognizer проще, надёжнее и предпочтительнее для стандартных жестов; touchesBegan оправдан только для кастомной графики.

Gesture Recognizer работает в SwiftUI?

В нативном SwiftUI используются модификаторы: onTapGesture, onLongPressGesture, DragGesture, MagnificationGesture, RotationGesture. Это декларативные аналоги UIGestureRecognizer, интегрированные в SwiftUI-иерархию. При необходимости можно обернуть UIKit-распознаватель через UIViewRepresentable, но Apple рекомендует использовать нативные SwiftUI-жесты.

Итоги

  • UIGestureRecognizer — абстракция UIKit для декомпозиции логики распознавания жестов в отдельные объекты с автоматическим управлением состояниями.
  • 7 встроенных подклассов покрывают 95% типовых жестов: тап, свайп, панорамирование, защемление, поворот, долгое нажатие и свайп от края.
  • Каждый распознаватель проходит 7 состояний — от .possible до .ended/.failed, что позволяет точно отслеживать дискретные и непрерывные жесты.
  • Метод require(toFail:) решает конфликты между жестами на одной вью, задавая приоритет распознавания.
  • В SwiftUI используются декларативные модификаторы жестов, но UIKit-распознаватели доступны через UIViewRepresentable.
  • Gesture Recognizer обязателен для UIKit-проектов; touchesBegan оправдан только для кастомного рисования и низкоуровневой графики.
  • Используйте require(toFail:) для пар tap/double-tap и gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) для параллельных жестов.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также