Punti chiave
Lo swipe è un movimento rapido e scorrevole del dito sulla superficie dello schermo, che termina con il sollevamento. Uno swipe può essere corto (sfogliare un elemento) o lungo (aprire una tendina), ma la caratteristica principale è la discretezza: il sistema lo riconosce come un singolo evento, non come una sequenza continua di coordinate.
I sistemi operativi mobili rilevano uno swipe in base a tre parametri: distanza minima (soglia), velocità minima (velocity) e direzione. Se il dito si muove lentamente o la distanza è insufficiente, il sistema interpreta il gesto come panoramica o tocco. I valori di soglia differiscono tra le piattaforme: iOS usa una costante in UISwipeGestureRecognizer con una soglia fissa, Android usa ViewConfiguration.getScaledTouchSlop(), che restituisce ~8–16 dp a seconda della densità dello schermo.
Secondo la Documentazione per sviluppatori Android, onFling (swipe) in GestureDetector si attiva a velocità superiori a 100 px/s e distanze da 50px. Presso IT Sectr, usiamo lo swipe per la navigazione (gallerie, interfacce a carte, onboarding) — questo gesto è intuitivo per utenti di tutte le età.
Lo swipe e la panoramica (pan/drag) sono diversi tipi di gesti, sebbene entrambi comportino lo spostamento di un dito sullo schermo. Lo swipe è discreto e significa "eseguire un'azione", mentre la panoramica è continua e significa "spostare un oggetto finché il dito è sullo schermo".
| Parametro | Swipe | Pan/Drag |
|---|---|---|
| Tipo | Discreto | Continuo |
| Eventi | Singolo (riconosciuto/non riconosciuto) | Multipli (.began → .changed → .ended) |
| Velocità | Alta (movimento rapido) | Qualsiasi (movimento lento) |
| Esempio iOS | UISwipeGestureRecognizer | UIPanGestureRecognizer |
| Esempio Android | GestureDetector.onFling() | GestureDetector.onScroll() |
| Scenario | Eliminare un'email, girare pagina | Spostare una mappa, trascinare un file |
| Tracciamento coordinate | No (solo direzione) | Sì (aggiornamento costante delle coordinate) |
La scelta tra swipe e panoramica dipende dal compito. Per azioni su un elemento (eliminare, archiviare, aprire menu) usa lo swipe. Per la manipolazione diretta (spostare, ridimensionare) usa la panoramica. Mescolare gesti sullo stesso elemento richiede require(toFail:) o logica personalizzata.
UISwipeGestureRecognizer è una sottoclasse di UIGestureRecognizer che riconosce uno swipe discreto in una delle quattro direzioni. A differenza di UIPanGestureRecognizer, non genera eventi .changed intermedi: quando riconosciuto, l'azione viene chiamata esattamente una volta.
La proprietà direction (UISwipeGestureRecognizer.Direction) imposta la direzione tracciata — .left, .right, .up, .down. Le direzioni possono essere combinate tramite una maschera: [.left, .right] per gli swipe orizzontali. La proprietà numberOfTouchesRequired determina il numero di dita — 1 per impostazione predefinita. UISwipeGestureRecognizer non ha una soglia di distanza configurabile — iOS utilizza un valore incorporato di circa 20px, sufficiente per un riconoscimento affidabile.
Poiché UISwipeGestureRecognizer è un riconoscitore discreto, non entra in conflitto con UIPanGestureRecognizer: il movimento lento attiva pan, quello rapido attiva swipe. Tuttavia, in coesistenza con UITapGestureRecognizer sulla stessa vista, potrebbe essere necessario require(toFail:) per prevenire un falso riconoscimento del tocco.
In Android, l'API principale per il riconoscimento dello swipe è GestureDetector con il callback OnGestureListener, in particolare il metodo onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY). GestureDetector analizza la posizione iniziale e finale del dito, la velocità di movimento lungo gli assi e la distanza, quindi chiama onFling quando le soglie vengono superate.
Le soglie minime sono configurabili tramite ViewConfiguration: VelocityTracker.getMinVelocity() (~100 px/s) e ViewConfiguration.getScaledTouchSlop() (~8–16 dp). Un OnSwipeTouchListener personalizzato è un pattern comune che semplifica l'API: lo sviluppatore non deve calcolare manualmente direzione e velocità. La Android Support Library non contiene un "SwipeDetector" incorporato, quindi un wrapper personalizzato è la soluzione standard.
Per lo swipe nelle liste, Google raccomanda di usare ItemTouchHelper.SimpleCallback di RecyclerView, che astrae il rilevamento del gesto, l'animazione e i callback onSwiped(). Presso IT Sectr, usiamo un SwipeListener personalizzato per ViewPager2 e interfacce a carte — questo dà controllo sulla sensibilità dello swipe senza sacrificare le prestazioni.
Per le liste (UITableView, RecyclerView), Apple e Google forniscono API specializzate che sostituiscono l'aggiunta manuale di UISwipeGestureRecognizer. Queste API supportano pulsanti di azione, animazione personalizzata e gesti "scorri per eliminare" — pattern standard nelle applicazioni mobili.
In iOS (11+), si usa UISwipeActionsConfiguration: il metodo tableView(_:leadingSwipeActionsConfigurationForRowAt:) restituisce un array di UIContextualAction — pulsanti con titolo, stile (destructive, normal) e gestore. Ad esempio, "Elimina" — .destructive con sfondo rosso, "Archivia" — .normal con sfondo grigio. Il sistema anima la comparsa dei pulsanti e chiama il blocco completion dopo un tocco.
In Android, ItemTouchHelper.SimpleCallback viene allegato a RecyclerView tramite ItemTouchHelper.attachToRecyclerView(). I parametri includono flag di direzione (start, end, up, down) e il callback onSwiped(viewHolder, direction). All'interno di onSwiped, viene chiamato adapter.notifyItemRemoved(position) per rimuovere l'elemento con animazione. ItemTouchHelper gestisce automaticamente i gesti, i conflitti di scorrimento e il ritorno dell'elemento se lo swipe non viene completato. Secondo Google I/O 2024, ItemTouchHelper è utilizzato nel 70% delle app Android con azioni di swipe nelle liste.
Aggiunge UISwipeGestureRecognizer a una vista per tornare alla schermata precedente. Il codice mostra la configurazione minima del riconoscitore con la direzione .left.
import UIKit
class CardViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let swipeLeft = UISwipeGestureRecognizer(
target: self,
action: #selector(handleSwipeLeft(_:))
)
swipeLeft.direction = .left
swipeLeft.numberOfTouchesRequired = 1
view.addGestureRecognizer(swipeLeft)
}
@objc private func handleSwipeLeft(_: UISwipeGestureRecognizer) {
guard let nav = navigationController else { return }
if nav.viewControllers.count > 1 {
nav.popViewController(animated: true)
} else {
nav.dismiss(animated: true)
}
}
}
Il riconoscitore viene allegato alla vista radice del controller. Il controllo navigationController.viewControllers.count garantisce che pop venga eseguito solo quando è presente una schermata precedente nello stack. Per i controller modali, viene usato dismiss. Il codice non richiede controllo dello stato — UISwipeGestureRecognizer è discreto e chiama l'azione solo dopo il riconoscimento riuscito.
Implementa un semplice wrapper di GestureDetector che determina la direzione dello swipe e chiama i callback onSwipeLeft/Right.
import android.content.Context
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
open class OnSwipeTouchListener(context: Context) : View.OnTouchListener {
private val gestureDetector = GestureDetector(context, GestureListener())
override fun onTouch(v: View, event: MotionEvent): Boolean {
return gestureDetector.onTouchEvent(event)
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
companion object {
private const val SWIPE_THRESHOLD = 100
private const val SWIPE_VELOCITY_THRESHOLD = 100
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
val diffX = e2.x - e1!!.x
val diffY = e2.y - e1.y
if (Math.abs(diffX) > Math.abs(diffY)) {
if (Math.abs(diffX) > SWIPE_THRESHOLD
&& Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD
) {
if (diffX > 0) onSwipeRight()
else onSwipeLeft()
return true
}
} else {
if (Math.abs(diffY) > SWIPE_THRESHOLD
&& Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD
) {
if (diffY > 0) onSwipeDown()
else onSwipeUp()
return true
}
}
return false
}
}
open fun onSwipeLeft() {}
open fun onSwipeRight() {}
open fun onSwipeUp() {}
open fun onSwipeDown() {}
}
OnSwipeTouchListener estende SimpleOnGestureListener, implementando onFling. diffX/diffY determinano la direzione, SWIPE_THRESHOLD (100px) e SWIPE_VELOCITY_THRESHOLD (100 px/s) sono le soglie. Utilizzo: view.setOnTouchListener(OnSwipeTouchListener(context).apply { onSwipeLeft = { dismiss() } }).
Aggiunge un pulsante di eliminazione quando si scorre a sinistra su una cella della tabella. Utilizza l'API integrata di iOS 11+ senza riconoscimento manuale dei gesti.
func tableView(
_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(
style: .destructive,
title: "Elimina"
) { _, _, completionHandler in
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
completionHandler(true)
}
deleteAction.backgroundColor = .systemRed
let config = UISwipeActionsConfiguration(actions: [deleteAction])
config.performsFirstActionWithFullSwipe = true
return config
}
Il metodo restituisce UISwipeActionsConfiguration con un array di UIContextualAction. style: .destructive imposta automaticamente lo sfondo rosso. performsFirstActionWithFullSwipe = true consente di eseguire l'azione con uno swipe completo senza tocco aggiuntivo. Presso IT Sectr, usiamo questa API per tutte le liste con azioni — è standardizzata, supporta VoiceOver e non richiede GestureRecognizer personalizzati.
Domande frequenti
Lo swipe è un gesto discreto con movimento rapido e sollevamento del dito, la panoramica è un trascinamento continuo con tracciamento delle coordinate. UISwipeGestureRecognizer si attiva una volta, UIPanGestureRecognizer genera .began → .changed → .ended. La scelta dipende dallo scenario: swipe per azioni (eliminare, girare), pan per movimento (mappa, cursore).
La logica del gesto è identica, ma l'API differisce. iOS usa UISwipeGestureRecognizer con direction. Android usa GestureDetector.onFling() con soglie personalizzate. Il risultato è la stessa UX con implementazioni diverse. I framework multipiattaforma (Flutter, React Native) astraggono la differenza attraverso un'API di swipe unificata.
In iOS (11+), usa UISwipeActionsConfiguration in UITableViewDelegate. In Android, usa ItemTouchHelper.SimpleCallback per RecyclerView. Queste API sostituiscono i GestureRecognizer manuali, supportano pulsanti di azione e animazione, gestiscono correttamente i conflitti di scorrimento e si adattano alle HIG della piattaforma.
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