Головне
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 проходить через 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 — це дозволяє розрізняти початок, зміну та кінець жесту.
UIKit надає 7 вбудованих підкласів UIGestureRecognizer, що покривають більшість сценаріїв взаємодії. Кожен підклас має специфічні налаштування: numberOfTapsRequired для тапа, direction для свайпа, minimumPressDuration для довгого натискання.
Для кастомних жестів (наприклад, малювання зигзага) створюється підклас 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:) — це повністю виключило баги з подвійним спрацьовуванням тапів.
Додає обробник одиничного тапа на UIImageView. При тапі зображення змінює прозорість — найпростіший приклад, що показує прив'язку жесту до в'ю.
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:) для визначення координат тапа.
Реалізує свайп вліво для повернення на попередній екран. Демонструє налаштування напрямку та прив'язку жесту до root-в'ю контролера.
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. Для підтримки кількох напрямків створіть окремі розпізнавачі на кожне.
Показує, як налаштувати одиничний та подвійний тап на одній в'ю без конфлікту. Розпізнавач подвійного тапа має пріоритет — одиничний тап спрацьовує тільки якщо подвійний не розпізнано.
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 цей патерн використовується в редакторах зображень та галереях, де подвійний тап зумує, а одиничний — виділяє елемент.
Часто задавані питання
Так, UIView підтримує безліч UIGestureRecognizer одночасно. Для вирішення конфліктів використовуйте метод require(toFail:), який задає порядок розпізнавання. Для паралельної роботи жестів (наприклад, pan + pinch на карті) реалізуйте делегат gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:), що повертає true.
UIGestureRecognizer — високорівнева абстракція, що розпізнає патерни дотиків автоматично та керує станами. touchesBegan — низькорівневий метод UIResponder, що вимагає ручного відстеження координат, таймінгу та скасування дотиків. Gesture Recognizer простіший, надійніший і кращий для стандартних жестів; touchesBegan виправданий тільки для кастомної графіки.
В нативному SwiftUI використовуються модифікатори: onTapGesture, onLongPressGesture, DragGesture, MagnificationGesture, RotationGesture. Це декларативні аналоги UIGestureRecognizer, інтегровані в SwiftUI-ієрархію. При необхідності можна обернути UIKit-розпізнавач через UIViewRepresentable, але Apple рекомендує використовувати нативні SwiftUI-жести.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також