Principalele puncte
UIGestureRecognizer — o clasă de bază abstractă în UIKit care descompune procesul de recunoaștere a gesturilor în obiecte separate. Fiecare subclasă răspunde pentru un tip de gest: UITapGestureRecognizer — pentru tap cu un număr specific de degete, UISwipeGestureRecognizer — pentru glisarea într-o direcție dată. Dezvoltatorul adaugă recunoscătorul pe UIView prin metoda addGestureRecognizer(:), iar UIKit urmărește automat atingerile, actualizează starea și apelează acțiunea la recunoaștere.
Înainte de apariția UIGestureRecognizer (iOS 3.2, 2010), dezvoltatorii suprascriau metodele UIResponder — touchesBegan, touchesMoved, touchesEnded — și analizau manual traiectoria atingerilor. Aceasta ducea la duplicarea codului și erori la procesarea atingerilor simultane. Apple a încapsulat această logică în UIGestureRecognizer, adăugând suport pentru multitouch, anularea gesturilor și funcționarea simultană a mai multor recunoscători pe aceeași vizualizare.
Conform sesiunilor WWDC, Gesture Recognizer procesează până la 11 atingeri simultane pe iPad și 5 pe iPhone. În IT Sectr folosim UIGestureRecognizer ca metodă standard de procesare a intrărilor utilizatorului în toate proiectele UIKit — aceasta a eliminat bug-urile legate de urmărirea manuală a touchesBegan.
Fiecare UIGestureRecognizer trece prin 7 stări posibile, definite în enum-ul UIGestureRecognizer.State. Aceste stări reflectă ciclul de viață al recunoașterii: de la detectarea atingerii până la finalizarea sau anularea gestului. Înțelegerea stărilor este critică pentru implementarea recunoscătorilor personalizați și depanarea conflictelor.
| Stare | Semnificație | Când apare |
|---|---|---|
| .possible | Stare inițială, gestul nu a fost încă recunoscut | Imediat după adăugarea pe vizualizare |
| .began | Gestul a fost recunoscut și a început să se execute | La prima mișcare a degetului pentru pan/longPress |
| .changed | Parametrii gestului s-au schimbat (coordonate, unghi) | La fiecare mișcare a degetului |
| .ended | Utilizatorul a ridicat degetul, gestul s-a încheiat | La touchesEnded |
| .cancelled | Gestul a fost întrerupt de sistem (apel primit, schimbare orientare) | La touchesCancelled |
| .failed | Gestul nu a fost recunoscut conform condițiilor | La touchesCancelled fără recunoaștere |
| .recognized | Sinonim pentru .ended; gestul a fost recunoscut cu succes | La fel ca .ended |
Gesturile discrete (tap, swipe) trec din .possible direct în .ended sau .failed. Gesturile continue (pan, pinch, rotation, longPress) trec prin .possible → .began → .changed (de mai multe ori) → .ended. În metoda action, verificați gestureRecognizer.state — acest lucru permite diferențierea începutului, modificării și sfârșitului gestului.
UIKit oferă 7 subclase încorporate ale UIGestureRecognizer, care acoperă majoritatea scenariilor de interacțiune. Fiecare subclasă are setări specifice: numberOfTapsRequired pentru tap, direction pentru glisare, minimumPressDuration pentru apăsare lungă.
Pentru gesturi personalizate (de exemplu, desenarea unui zigzag) se creează o subclasă a UIGestureRecognizer cu suprascrierea metodelor touchesBegan, touchesMoved, touchesEnded și actualizarea stării. Apple recomandă utilizarea claselor încorporate acolo unde este posibil — sunt optimizate și interacționează corect între ele.
Când pe aceeași UIView sunt utilizate mai multe UIGestureRecognizer (de exemplu, tap și double-tap), apare un conflict de recunoaștere: la dublu tap, mai întâi se declanșează tapul simplu. Pentru rezolvare, Apple oferă metoda require(toFail:), care întârzie recunoașterea unui gest până când celălalt se termină cu eșec.
Mecanismul funcționează astfel: apelând tapRecognizer.require(toFail: doubleTapRecognizer), specificați că tapRecogniser va trece în starea .recognized numai după ce doubleTapRecogniser se termină în .failed. Aceasta adaugă o întârziere de ~0.3 secunde înainte de executarea tapului simplu — utilizatorul atinge de două ori, iar primul tap este ignorat. Metoda alternativă — delegatul UIGestureRecognizerDelegate cu metoda gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:), care permite recunoașterea simultană (de exemplu, pan + pinch pentru hartă).
Potrivit WWDC 2020, aproximativ 15% din bug-uri în aplicațiile UIKit sunt legate de configurarea incorectă a conflictelor de gesturi. În IT Sectr am standardizat abordarea: pentru fiecare ecran se creează o schemă de gesturi cu prioritățile require(toFail:) — aceasta a eliminat complet bug-urile cu declanșarea dublă a tapurilor.
Adaugă un handler de tap simplu pe UIImageView. La tap, imaginea își schimbă transparența — exemplul cel mai simplu care arată atașarea unui gest la o vizualizare.
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
}
}
}
Momentul cheie: isUserInteractionEnabled pe UIImageView este implicit false — fără această flag, Gesture Recognizer nu va primi atingeri. Pentru UIView și UIButton flag-ul este activat implicit. Metoda action primește un parametru UITapGestureRecognizer prin care se poate obține location(in:) pentru determinarea coordonatelor tapului.
Implementează glisarea la stânga pentru revenirea la ecranul anterior. Demonstrează configurarea direcției și atașarea gestului la vizualizarea rădăcină a controlerului.
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 — un gest discret: trece în .recognised imediat după recunoaștere, fără .changed intermediar. Prin urmare, în action nu trebuie verificată starea — gestul este fie recunoscut (se apelează acțiunea), fie nu. Proprietatea direction acceptă una dintre cele patru valori: .left, .right, .up, .down. Pentru a suporta mai multe direcții, creați recunoscători separați pentru fiecare.
Arată cum să configurați tapul simplu și dublu pe aceeași vizualizare fără conflict. Recunoscătorul de tap dublu are prioritate — tapul simplu se declanșează numai dacă tapul dublu nu a fost recunoscut.
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")
}
}
Fără require(toFail:) la tapul dublu, mai întâi se va apela handleSingleTap, apoi handleDoubleTap — acest lucru strică UX-ul. Cu require(toFail:) tapul simplu așteaptă ~0.3 secunde pentru a se asigura că al doilea tap nu urmează. În IT Sectr acest model este folosit în editoarele de imagini și galerii, unde tapul dublu mărește, iar cel simplu — selectează un element.
Întrebări frecvente
Da, UIView suportă mai multe UIGestureRecognizer simultan. Pentru rezolvarea conflictelor, folosiți metoda require(toFail:), care stabilește ordinea de recunoaștere. Pentru funcționarea paralelă a gesturilor (de exemplu, pan + pinch pe o hartă) implementați delegatul gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:), care returnează true.
UIGestureRecognizer — o abstractizare de nivel înalt care recunoaște automat modelele de atingeri și gestionează stările. touchesBegan — o metodă de nivel scăzut a UIResponder care necesită urmărirea manuală a coordonatelor, sincronizării și anulării atingerilor. Gesture Recognizer este mai simplu, mai fiabil și preferabil pentru gesturile standard; touchesBegan este justificat doar pentru grafica personalizată.
În SwiftUI nativ se folosesc modificatori: onTapGesture, onLongPressGesture, DragGesture, MagnificationGesture, RotationGesture. Aceștia sunt analogi declaraivi ai UIGestureRecognizer, integrați în ierarhia SwiftUI. La nevoie, puteți împacheta un recunoscător UIKit prin UIViewRepresentable, dar Apple recomandă utilizarea gesturilor native SwiftUI.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și