Основни точки
UIGestureRecognizer — абстрактен базов клас в UIKit, който декомпозира процеса на разпознаване на жестове в отделни обекти. Всеки подклас отговаря за един тип жест: UITapGestureRecognizer — за тап с определен брой пръсти, UISwipeGestureRecognizer — за преместване в зададена посока. Разработчикът добавя разпознавача към UIView чрез метода addGestureRecognizer(:), и UIKit автоматично проследява докосванията, актуализира състоянието и извиква действие при разпознаване.
Преди появата на 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 и актуализиране на състоянието. Apple препоръчва използването на вградени класове, където е възможно — те са оптимизирани и взаимодействат правилно помежду си.
Когато на един UIView се използват няколко UIGestureRecognizer (например tap и double-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:) — това напълно елиминира грешките с двойно задействане на тапове.
Добавя обработчик на единичен тап към 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:) за определяне на координатите на тапа.
Имплементира преместване наляво за връщане към предишния екран. Демонстрира настройка на посоката и свързване на жеста с кореновия изглед на контролера.
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. Следователно в действието не е необходимо да се проверява състоянието — жестът или е разпознат (действието се извиква), или не. Свойството 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 — това разваля потребителското изживяване. С 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също