Najważniejsze
UIGestureRecognizer — abstrakcyjna klasa bazowa w UIKit, dekomponująca proces rozpoznawania gestów na osobne obiekty. Każda podklasa odpowiada za jeden typ gestu: UITapGestureRecognizer — za tap określonej liczby palców, UISwipeGestureRecognizer — za przesunięcie w zadanym kierunku. Programista dodaje rozpoznawacz do UIView przez metodę addGestureRecognizer(:), a UIKit automatycznie śledzi dotknięcia, aktualizuje stan i wywołuje akcję po rozpoznaniu.
Przed pojawieniem się UIGestureRecognizer (iOS 3.2, 2010) programiści nadpisywali metody UIResponder — touchesBegan, touchesMoved, touchesEnded — i ręcznie analizowali trajektorię dotknięć. Prowadziło to do duplikacji kodu i błędów przy obsłudze jednoczesnych dotknięć. Apple enkapsulowało tę logikę w UIGestureRecognizer, dodając obsługę multitouch, anulowania gestów i jednoczesnej pracy wielu rozpoznawaczy na jednym widoku.
Według sesji WWDC, Gesture Recognizer obsługuje do 11 jednoczesnych dotknięć na iPadzie i 5 na iPhonie. W IT Sectr używamy UIGestureRecognizer jako standardowego sposobu obsługi wprowadzania danych przez użytkownika we wszystkich projektach UIKit — wyeliminowało to błędy związane z ręcznym śledzeniem touchesBegan.
Każdy UIGestureRecognizer przechodzi przez 7 możliwych stanów, zdefiniowanych w enum UIGestureRecognizer.State. Te stany odzwierciedlają cykl życia rozpoznawania: od określenia dotknięcia do zakończenia lub anulowania gestu. Zrozumienie stanów jest kluczowe dla implementacji niestandardowych rozpoznawaczy i debugowania konfliktów.
| Stan | Znaczenie | Kiedy występuje |
|---|---|---|
| .possible | Stan początkowy, gest jeszcze nierozpoznany | Natychmiast po dodaniu do widoku |
| .began | Gest rozpoznany i rozpoczęty | Przy pierwszym ruchu palca dla pan/longPress |
| .changed | Parametry gestu uległy zmianie (współrzędne, kąt) | Przy każdym ruchu palca |
| .ended | Użytkownik zdjął palec, gest zakończony | Przy touchesEnded |
| .cancelled | Gest przerwany przez system (połączenie przychodzące, zmiana orientacji) | Przy touchesCancelled |
| .failed | Gest nierozpoznany zgodnie z warunkami | Przy touchesCancelled bez rozpoznania |
| .recognized | Synonim .ended; gest pomyślnie rozpoznany | To samo co .ended |
Gesty dyskretne (tap, swipe) przechodzą z .possible bezpośrednio do .ended lub .failed. Gesty ciągłe (pan, pinch, rotation, longPress) przechodzą .possible → .began → .changed (wielokrotnie) → .ended. W metodzie akcji sprawdzaj gestureRecognizer.state — to pozwala odróżnić początek, zmianę i koniec gestu.
UIKit udostępnia 7 wbudowanych podklas UIGestureRecognizer, pokrywających większość scenariuszy interakcji. Każda podklasa ma specyficzne ustawienia: numberOfTapsRequired dla tapu, direction dla przesunięcia, minimumPressDuration dla długiego naciśnięcia.
Do niestandardowych gestów (np. rysowanie zygzaka) tworzy się podklasę UIGestureRecognizer z nadpisaniem metod touchesBegan, touchesMoved, touchesEnded i aktualizacją stanu. Apple zaleca używanie wbudowanych klas, gdzie to możliwe — są zoptymalizowane i poprawnie współdziałają ze sobą.
Gdy na jednym UIView używanych jest kilka UIGestureRecognizer (np. tap i double-tap), powstaje konflikt rozpoznawania: przy podwójnym tapie najpierw uruchamia się pojedynczy tap. Do rozwiązania Apple udostępnia metodę require(toFail:), która opóźnia rozpoznanie jednego gestu, dopóki drugi nie zakończy się niepowodzeniem.
Mechanizm działa następująco: wywołując tapRecognizer.require(toFail: doubleTapRecognizer), określasz, że tapRecogniser przejdzie do stanu .recognized dopiero po tym, jak doubleTapRecogniser zakończy się w .failed. To dodaje opóźnienie ~0.3 sekundy przed wykonaniem pojedynczego tapu — użytkownik naciska dwukrotnie, a pierwszy tap jest ignorowany. Alternatywną metodą jest delegat UIGestureRecognizerDelegate z metodą gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:), która zezwala na jednoczesne rozpoznawanie (np. pan + pinch dla mapy).
Według WWDC 2020, około 15% błędów w aplikacjach UIKit jest związanych z nieprawidłową konfiguracją konfliktów gestów. W IT Sectr standaryzowaliśmy podejście: dla każdego ekranu tworzony jest schemat gestów z określonymi priorytetami require(toFail:) — to całkowicie wyeliminowało błędy z podwójnym uruchamianiem tapów.
Dodaje obsługę pojedynczego tapu na UIImageView. Po tapnięciu obraz zmienia przezroczystość — najprostszy przykład pokazujący przypisanie gestu do widoku.
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
}
}
}
Kluczowy moment: isUserInteractionEnabled na UIImageView domyślnie ma wartość false — bez tej flagi Gesture Recognizer nie będzie otrzymywać dotknięć. Dla UIView i UIButton flaga jest domyślnie włączona. Metoda akcji przyjmuje parametr UITapGestureRecognizer, przez który można uzyskać location(in:) do określenia współrzędnych tapu.
Implementuje przesunięcie w lewo, aby powrócić do poprzedniego ekranu. Demonstruje konfigurację kierunku i przypisanie gestu do widoku głównego kontrolera.
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 — gest dyskretny: przechodzi do .recognised natychmiast po rozpoznaniu, bez pośrednich .changed. Dlatego w akcji nie trzeba sprawdzać stanu — gest albo jest rozpoznany (wywołanie akcji), albo nie. Właściwość direction przyjmuje jedną z czterech wartości: .left, .right, .up, .down. Aby obsługiwać wiele kierunków, utwórz osobne rozpoznawacze dla każdego.
Pokazuje, jak skonfigurować pojedynczy i podwójny tap na jednym widoku bez konfliktu. Rozpoznawacz podwójnego tapu ma priorytet — pojedynczy tap uruchamia się tylko wtedy, gdy podwójny nie został rozpoznany.
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")
}
}
Bez require(toFail:) przy podwójnym tapie najpierw wywoła się handleSingleTap, potem handleDoubleTap — to psuje UX. Z require(toFail:) pojedynczy tap czeka ~0.3 sekundy, aby upewnić się, że drugi tap nie nastąpi. W IT Sectr ten wzorzec jest używany w edytorach obrazów i galeriach, gdzie podwójny tap przybliża, a pojedynczy — zaznacza element.
Często zadawane pytania
Tak, UIView obsługuje wiele UIGestureRecognizer jednocześnie. Do rozwiązywania konfliktów użyj metody require(toFail:), która określa kolejność rozpoznawania. Do równoległej pracy gestów (np. pan + pinch na mapie) zaimplementuj delegata gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:), zwracającego true.
UIGestureRecognizer — wysokopoziomowa abstrakcja, która automatycznie rozpoznaje wzorce dotknięć i zarządza stanami. touchesBegan — niskopoziomowa metoda UIResponder, wymagająca ręcznego śledzenia współrzędnych, czasów i anulowania dotknięć. Gesture Recognizer jest prostszy, bardziej niezawodny i preferowany dla standardowych gestów; touchesBegan jest uzasadniony tylko dla niestandardowej grafiki.
W natywnym SwiftUI używa się modyfikatorów: onTapGesture, onLongPressGesture, DragGesture, MagnificationGesture, RotationGesture. Są to deklaratywne odpowiedniki UIGestureRecognizer, zintegrowane z hierarchią SwiftUI. W razie potrzeby można opakować UIKit-rozpoznawacz przez UIViewRepresentable, ale Apple zaleca używanie natywnych gestów SwiftUI.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również