Punti chiave
UIGestureRecognizer è una classe base astratta in UIKit che scompone il processo di riconoscimento dei gesti in oggetti separati. Ogni sottoclasse gestisce un tipo di gesto: UITapGestureRecognizer gestisce un tap con un numero specifico di tocchi, UISwipeGestureRecognizer gestisce uno swipe in una direzione determinata. Lo sviluppatore aggiunge il riconoscitore a UIView tramite il metodo addGestureRecognizer(:), e UIKit traccia automaticamente i tocchi, aggiorna lo stato e chiama un'azione al riconoscimento.
Prima di UIGestureRecognizer (iOS 3.2, 2010), gli sviluppatori sovrascrivevano i metodi UIResponder — touchesBegan, touchesMoved, touchesEnded — e analizzavano manualmente le traiettorie dei tocchi. Ciò causava duplicazione di codice ed errori nella gestione dei tocchi simultanei. Apple ha incapsulato questa logica in UIGestureRecognizer, aggiungendo il supporto per multitouch, annullamento dei gesti e funzionamento simultaneo di più riconoscitori su una stessa vista.
Secondo le sessioni WWDC, Gesture Recognizer gestisce fino a 11 tocchi simultanei su iPad e 5 su iPhone. In IT Sectr, usiamo UIGestureRecognizer come metodo standard per gestire l'input utente in tutti i progetti UIKit — ciò ha eliminato i bug associati al tracciamento manuale di touchesBegan.
Ogni UIGestureRecognizer attraversa 7 stati possibili, definiti nell'enum UIGestureRecognizer.State. Questi stati riflettono il ciclo di vita del riconoscimento: dalla rilevazione del tocco al completamento o all'annullamento. Comprendere gli stati è fondamentale per implementare riconoscitori personalizzati e debug dei conflitti.
| Stato | Significato | Quando si verifica |
|---|---|---|
| .possible | Stato iniziale, gesto non ancora riconosciuto | Subito dopo essere stato aggiunto a una vista |
| .began | Gesto riconosciuto e iniziato a eseguire | Al primo movimento del dito per pan/longPress |
| .changed | Parametri del gesto cambiati (coordinate, angolo) | Ad ogni movimento del dito |
| .ended | L'utente ha sollevato il dito, gesto completato | A touchesEnded |
| .cancelled | Gesto interrotto dal sistema (chiamata in arrivo, cambio di orientamento) | A touchesCancelled |
| .failed | Gesto non riconosciuto secondo le condizioni | A touchesCancelled senza riconoscimento |
| .recognized | Sinonimo di .ended; gesto riconosciuto con successo | Come .ended |
I gesti discreti (tap, swipe) passano da .possible direttamente a .ended o .failed. I gesti continui (pan, pinch, rotation, longPress) attraversano .possible → .began → .changed (ripetutamente) → .ended. Nel metodo di azione, controlla gestureRecognizer.state — questo permette di distinguere l'inizio, il cambiamento e la fine di un gesto.
UIKit fornisce 7 sottoclassi integrate di UIGestureRecognizer, coprendo la maggior parte degli scenari di interazione. Ogni sottoclasse ha impostazioni specifiche: numberOfTapsRequired per il tap, direction per lo swipe, minimumPressDuration per la pressione lunga.
Per gesti personalizzati (ad esempio, disegnare uno zigzag), si crea una sottoclasse di UIGestureRecognizer sovrascrivendo touchesBegan, touchesMoved, touchesEnded e aggiornando lo stato. Apple raccomanda di usare le classi integrate dove possibile — sono ottimizzate e interagiscono correttamente tra loro.
Quando più UIGestureRecognizer vengono usati su una stessa UIView (ad esempio, tap e doppio tap), si verifica un conflitto di riconoscimento: su un doppio tap, il tap singolo si attiva per primo. Apple fornisce il metodo require(toFail:) per ritardare il riconoscimento di un gesto finché un altro non fallisce.
Il meccanismo funziona così: chiamando tapRecognizer.require(toFail: doubleTapRecognizer), si specifica che tapRecognizer passerà allo stato .recognized solo dopo che doubleTapRecognizer termina in .failed. Questo aggiunge un ritardo di ~0,3 secondi prima di eseguire il tap singolo — l'utente tocca due volte e il primo tap viene ignorato. Un approccio alternativo è il delegato UIGestureRecognizerDelegate con il metodo gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:), che permette il riconoscimento simultaneo (ad esempio, pan + pinch per una mappa).
Secondo la WWDC 2020, circa il 15% dei bug nelle applicazioni UIKit è correlato a una configurazione errata dei conflitti di gesti. In IT Sectr, abbiamo standardizzato l'approccio: ogni schermata ha uno schema di gesti con priorità require(toFail:) — ciò ha eliminato completamente i bug di doppia attivazione dei tap.
Aggiunge un gestore di tap singolo a UIImageView. Al tocco, l'opacità dell'immagine cambia — un semplice esempio che mostra il collegamento di un gesto a una vista.
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
}
}
}
Punto chiave: isUserInteractionEnabled su UIImageView è false per impostazione predefinita — senza questo flag, Gesture Recognizer non riceverà i tocchi. Per UIView e UIButton, questo flag è attivato per impostazione predefinita. Il metodo di azione accetta un parametro UITapGestureRecognizer, attraverso il quale si può ottenere location(in:) per determinare le coordinate del tap.
Implementa uno swipe a sinistra per tornare alla schermata precedente. Mostra la configurazione della direzione e il collegamento del gesto alla vista radice del controller.
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 gesto discreto: passa a .recognized immediatamente dopo il riconoscimento, senza stati .changed intermedi. Pertanto, non è necessario controllare lo stato nell'azione — il gesto o viene riconosciuto (l'azione si attiva) o no. La proprietà direction accetta uno dei quattro valori: .left, .right, .up, .down. Per supportare più direzioni, crea riconoscitori separati per ciascuna.
Mostra come configurare tap singolo e doppio su una stessa vista senza conflitto. Il riconoscitore di doppio tap ha priorità — il tap singolo si attiva solo se il doppio tap non viene riconosciuto.
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")
}
}
Senza require(toFail:), su un doppio tap, handleSingleTap si attiva prima, poi handleDoubleTap — questo compromette l'UX. Con require(toFail:), il tap singolo attende ~0,3 secondi per assicurarsi che non segua un secondo tap. In IT Sectr, questo pattern viene utilizzato negli editor di immagini e nelle gallerie, dove un doppio tap ingrandisce e un tap singolo seleziona un elemento.
Domande frequenti
Sì, UIView supporta più istanze di UIGestureRecognizer simultaneamente. Per risolvere i conflitti, usa il metodo require(toFail:) che imposta l'ordine di riconoscimento. Per il funzionamento parallelo dei gesti (ad esempio, pan + pinch su una mappa), implementa il metodo delegato gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) restituendo true.
UIGestureRecognizer è un'astrazione di alto livello che riconosce automaticamente i pattern di tocco e gestisce gli stati. touchesBegan è un metodo di basso livello di UIResponder che richiede tracciamento manuale delle coordinate, temporizzazione e annullamento dei tocchi. Gesture Recognizer è più semplice, affidabile e preferibile per i gesti standard; touchesBegan è giustificato solo per grafica personalizzata.
SwiftUI nativo usa modificatori: onTapGesture, onLongPressGesture, DragGesture, MagnificationGesture, RotationGesture. Questi sono analoghi dichiarativi di UIGestureRecognizer integrati nella gerarchia SwiftUI. Se necessario, è possibile avvolgere un riconoscitore UIKit tramite UIViewRepresentable, ma Apple raccomanda di usare i gesti nativi di SwiftUI.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche