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), ви вказуєте, що tapRecognizer перейде в стан .recognized тільки після того, як doubleTapRecognizer завершиться в .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 — дискретний жест: він переходить в .recognized відразу після розпізнавання, без проміжних .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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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