Hlavní body
UIGestureRecognizer — abstraktní základní třída v UIKit, která rozkládá proces rozpoznávání gest na samostatné objekty. Každá podtřída je zodpovědná za jeden typ gesta: UITapGestureRecognizer — za tap určitým počtem prstů, UISwipeGestureRecognizer — za přejetí v daném směru. Vývojář přidá rozpoznávač na UIView pomocí metody addGestureRecognizer(:) a UIKit automaticky sleduje dotyky, aktualizuje stav a volá akci při rozpoznání.
Před příchodem UIGestureRecognizer (iOS 3.2, 2010) vývojáři přepisovali metody UIResponder — touchesBegan, touchesMoved, touchesEnded — a ručně analyzovali trajektorii dotyků. To vedlo k duplicitě kódu a chybám při zpracování současných dotyků. Apple tuto logiku zapouzdřil do UIGestureRecognizer a přidal podporu pro multitouch, rušení gest a současnou práci více rozpoznávačů na jednom zobrazení.
Podle relací WWDC zpracovává Gesture Recognizer až 11 současných dotyků na iPadu a 5 na iPhonu. V IT Sectr používáme UIGestureRecognizer jako standardní způsob zpracování vstupu uživatele ve všech UIKit projektech — to eliminovalo chyby spojené s ručním sledováním touchesBegan.
Každý UIGestureRecognizer prochází 7 možnými stavy, definovanými ve enumu UIGestureRecognizer.State. Tyto stavy odrážejí životní cyklus rozpoznávání: od detekce dotyku po dokončení nebo zrušení gesta. Pochopení stavů je kritické pro implementaci vlastních rozpoznávačů a ladění konfliktů.
| Stav | Význam | Kdy vzniká |
|---|---|---|
| .possible | Počáteční stav, gesto ještě nebylo rozpoznáno | Ihned po přidání do zobrazení |
| .began | Gesto bylo rozpoznáno a začalo se provádět | Při prvním pohybu prstu pro pan/longPress |
| .changed | Parametry gesta se změnily (souřadnice, úhel) | Při každém pohybu prstu |
| .ended | Uživatel zvedl prst, gesto bylo dokončeno | Při touchesEnded |
| .cancelled | Gesto bylo přerušeno systémem (příchozí hovor, změna orientace) | Při touchesCancelled |
| .failed | Gesto nebylo rozpoznáno podle podmínek | Při touchesCancelled bez rozpoznání |
| .recognized | Synonymum pro .ended; gesto bylo úspěšně rozpoznáno | Stejné jako .ended |
Diskrétní gesta (tap, swipe) přecházejí z .possible přímo do .ended nebo .failed. Spojitá gesta (pan, pinch, rotation, longPress) procházejí .possible → .began → .changed (vícekrát) → .ended. V metodě akce kontrolujte gestureRecognizer.state — to umožňuje rozlišit začátek, změnu a konec gesta.
UIKit poskytuje 7 vestavěných podtříd UIGestureRecognizer, které pokrývají většinu scénářů interakce. Každá podtřída má specifická nastavení: numberOfTapsRequired pro tap, direction pro přejetí, minimumPressDuration pro dlouhé stisknutí.
Pro vlastní gesta (např. kreslení cikcaku) se vytvoří podtřída UIGestureRecognizer s přepsáním metod touchesBegan, touchesMoved, touchesEnded a aktualizací stavu. Apple doporučuje používat vestavěné třídy, kde je to možné — jsou optimalizované a správně spolu interagují.
Když se na jednom UIView používá několik UIGestureRecognizer (např. tap a double-tap), vzniká konflikt rozpoznávání: při dvojitém tapu se nejprve aktivuje jednotlivý tap. K řešení Apple poskytuje metodu require(toFail:), která zpožďuje rozpoznání jednoho gesta, dokud druhé neskončí neúspěchem.
Mechanismus funguje následovně: voláním tapRecognizer.require(toFail: doubleTapRecognizer) určíte, že tapRecogniser přejde do stavu .recognized až poté, co doubleTapRecogniser skončí v .failed. To přidá zpoždění ~0,3 sekundy před provedením jednotlivého tapu — uživatel klepne dvakrát a první tap je ignorován. Alternativní metodou je delegát UIGestureRecognizerDelegate s metodou gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:), která povoluje současné rozpoznávání (např. pan + pinch pro mapu).
Podle WWDC 2020 souvisí přibližně 15% chyb v UIKit aplikacích s nesprávnou konfigurací konfliktů gest. V IT Sectr jsme standardizovali přístup: pro každou obrazovku se vytvoří schéma gest s prioritami require(toFail:) — to zcela eliminovalo chyby s dvojitým spouštěním tapů.
Přidá obsluhu jednotlivého tapu na UIImageView. Při tapu obrázek změní průhlednost — nejjednodušší příklad ukazující připojení gesta k zobrazení.
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
}
}
}
Klíčový bod: isUserInteractionEnabled na UIImageView je ve výchozím nastavení false — bez tohoto příznaku Gesture Recognizer nebude přijímat dotyky. Pro UIView a UIButton je příznak ve výchozím nastavení zapnutý. Metoda akce přijímá parametr UITapGestureRecognizer, přes který lze získat location(in:) pro určení souřadnic tapu.
Implementuje přejetí doleva pro návrat na předchozí obrazovku. Demonstruje nastavení směru a připojení gesta k kořenovému zobrazení kontroleru.
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 — diskrétní gesto: přechází do .recognised ihned po rozpoznání, bez mezilehlého .changed. Proto v akci není třeba kontrolovat stav — gesto je buď rozpoznáno (akce je volána), nebo není. Vlastnost direction přijímá jednu ze čtyř hodnot: .left, .right, .up, .down. Pro podporu více směrů vytvořte samostatné rozpoznávače pro každý směr.
Ukazuje, jak nakonfigurovat jednotlivý a dvojitý tap na stejném zobrazení bez konfliktu. Rozpoznávač dvojitého tapu má prioritu — jednotlivý tap se aktivuje pouze pokud dvojitý tap nebyl rozpoznán.
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:) se při dvojitém tapu nejprve zavolá handleSingleTap, poté handleDoubleTap — to kazí UX. S require(toFail:) jednotlivý tap čeká ~0,3 sekundy, aby se ujistil, že druhý tap nebude následovat. V IT Sectr se tento vzor používá v editorech obrázků a galeriích, kde dvojitý tap přibližuje a jednotlivý tap vybírá prvek.
Často kladené otázky
Ano, UIView podporuje více UIGestureRecognizer současně. Pro řešení konfliktů použijte metodu require(toFail:), která nastavuje pořadí rozpoznávání. Pro paralelní práci gest (např. pan + pinch na mapě) implementujte delegáta gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:), který vrací true.
UIGestureRecognizer — vysokourovňová abstrakce, která automaticky rozpoznává vzory dotyků a spravuje stavy. touchesBegan — nízkourovňová metoda UIResponder vyžadující ruční sledování souřadnic, časování a rušení dotyků. Gesture Recognizer je jednodušší, spolehlivější a upřednostňuje se pro standardní gesta; touchesBegan je ospravedlnitelný pouze pro vlastní grafiku.
V nativním SwiftUI se používají modifikátory: onTapGesture, onLongPressGesture, DragGesture, MagnificationGesture, RotationGesture. To jsou deklarativní protějšky UIGestureRecognizer integrované do hierarchie SwiftUI. V případě potřeby lze UIKit rozpoznávač zabalit přes UIViewRepresentable, ale Apple doporučuje používat nativní SwiftUI gesta.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také