Puncte principale
Swipe — o mișcare rapidă de alunecare a degetului pe suprafața ecranului, care se termină cu ridicarea. Swipe poate fi scurt (derularea unui element) sau lung (deschiderea unei cortine), dar caracteristica cheie — discretizarea: sistemul îl recunoaște ca un singur eveniment, nu ca o secvență continuă de coordonate.
Sistemele de operare mobile determină swipe-ul după trei parametri: distanța minimă (prag), viteza minimă (velocity) și direcția. Dacă degetul se mișcă lent sau distanța este insuficientă, sistemul interpretează gestul ca panoramare (pan) sau tap. Valorile prag diferă între platforme: iOS folosește constanta UISwipeGestureRecognizer cu un prag fix, Android — ViewConfiguration.getScaledTouchSlop(), care returnează ~8–16 dp în funcție de densitatea ecranului.
Conform Android Developers Documentation, onFling (swipe) în GestureDetector se activează la viteza peste 100 px/s și distanța de la 50px. În IT Sectr folosim swipe pentru navigare (galerii, interfețe card, onboarding) — acest gest este intuitiv pentru utilizatori de toate vârstele.
Swipe și panoramarea (pan/drag) — tipuri diferite de gesturi, deși ambele folosesc mișcarea degetului pe ecran. Swipe este discret și înseamnă „fă o acțiune”, în timp ce panoramarea este continuă și înseamnă „mută obiectul cât timp degetul este pe ecran”.
| Parametru | Swipe | Panoramare (Pan/Drag) |
|---|---|---|
| Tip | Discret | Continuu |
| Evenimente | Unul (recunoscut/nerrecunoscut) | Multiple (.began → .changed → .ended) |
| Viteză | Ridicată (mișcare rapidă) | Oricare (deplasare lentă) |
| Exemplu iOS | UISwipeGestureRecognizer | UIPanGestureRecognizer |
| Exemplu Android | GestureDetector.onFling() | GestureDetector.onScroll() |
| Scenariu | Șterge e-mail, întoarce pagina | Deplasa harta, trage fișierul |
| Urmărirea coordonatelor | Nu (doar direcție) | Da (actualizare continuă a coordonatelor) |
Alegerea între swipe și panoramare depinde de sarcină. Pentru acțiuni asupra elementului (ștergere, arhivare, deschidere meniu) folosiți swipe. Pentru manipulare directă (deplasare, scalare) — panoramare. Amestecarea gesturilor pe același element necesită require(toFail:) sau logică personalizată.
UISwipeGestureRecognizer — o subclasă a UIGestureRecognizer care recunoaște swipe-ul discret într-una din cele patru direcții. Spre deosebire de UIPanGestureRecognizer, nu generează evenimente intermediare .changed: la recunoaștere, acțiunea este apelată o singură dată.
Proprietatea direction (UISwipeGestureRecognizer.Direction) stabilește direcția urmărită — .left, .right, .up, .down. Se poate combina prin mască: [.left, .right] pentru swipe-uri orizontale. Proprietatea numberOfTouchesRequired determină numărul de degete — implicit 1. UISwipeGestureRecognizer nu are un prag de distanță configurabil — iOS folosește o valoare încorporată de aproximativ 20px, suficientă pentru o recunoaștere fiabilă.
Deoarece UISwipeGestureRecogniser este un recunoscător discret, nu intră în conflict cu UIPanGestureRecognizer: la mișcare lentă se activează pan, la mișcare rapidă — swipe. Totuși, la coexistența cu UITapGestureRecognizer pe aceeași vedere, poate fi necesar require(toFail:) pentru prevenirea recunoașterii false a tap-ului.
În Android, API-ul principal pentru recunoașterea swipe-ului este GestureDetector cu callback-ul OnGestureListener, și anume metoda onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY). GestureDetector analizează poziția inițială și finală a degetului, viteza de mișcare pe axe și distanța, apoi apelează onFling la depășirea pragurilor.
Pragurile minime se configurează prin ViewConfiguration: VelocityTracker.getMinVelocity() (~100 px/s) și ViewConfiguration.getScaledTouchSlop() (~8–16 dp). OnSwipeTouchListener personalizat — un model comun care simplifică API-ul: dezvoltatorul nu trebuie să calculeze manual direcția și viteza. Android Support Library nu conține un „SwipeDetector” încorporat, deci o împachetare personalizată este soluția standard.
Pentru swipe în liste, Google recomandă utilizarea ItemTouchHelper.SimpleCallback din RecyclerView, care abstrage detectarea gestului, animația și callback-urile onSwiped(). În IT Sectr folosim SwipeListener personalizat pentru ViewPager2 și interfețe card — oferind control asupra sensibilității swipe fără a afecta performanța.
Pentru liste (UITableView, RecyclerView) Apple și Google oferă API-uri specializate care înlocuiesc adăugarea manuală a UISwipeGestureRecognizer. Aceste API-uri suportă butoane de acțiune, animație personalizată și gesturi „deplasează pentru a șterge” — modele standard ale aplicațiilor mobile.
În iOS (11+) se folosește UISwipeActionsConfiguration: metoda tableView(_:leadingSwipeActionsConfigurationForRowAt:) returnează un array de UIContextualAction — butoane cu titlu, stil (destructive, normal) și handler. De exemplu, „Șterge” — .destructive cu fundal roșu, „Arhivă” — .normal cu gri. Sistemul animează singur apariția butoanelor și apelează blocul completion după apăsare.
În Android, ItemTouchHelper.SimpleCallback se atașează la RecyclerView prin ItemTouchHelper.attachToRecyclerView(). Parametri — flag-uri de direcție (start, end, up, down) și callback-ul onSwiped(viewHolder, direction). În interiorul onSwiped se apelează adapter.notifyItemRemoved(position) pentru ștergerea elementului cu animație. ItemTouchHelper gestionează automat gesturile, conflictele cu derularea (scroll) și returnarea elementului dacă swipe-ul nu este finalizat. Conform Google I/O 2024, ItemTouchHelper este utilizat în 70% din aplicațiile Android cu acțiuni de swipe în liste.
Adaugă UISwipeGestureRecognizer pe view pentru revenirea la ecranul anterior. Codul arată configurația minimă a recunoscătorului cu direcția .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)
}
}
}
Recunoscătorul se atașează la root-view-ul controller-ului. Verificarea navigationController.viewControllers.count garantează că pop se execută doar când există un ecran anterior în stivă. Pentru controllere modale se folosește dismiss. Codul nu necesită verificarea stării — UISwipeGestureRecognizer este discret și apelează action doar la recunoașterea cu succes.
Implementează o împachetare simplă a GestureDetector care determină direcția swipe-ului și apelează callback-urile 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 extinde SimpleOnGestureListener, implementând onFling. diffX/diffY determină direcția, SWIPE_THRESHOLD (100px) și SWIPE_VELOCITY_THRESHOLD (100 px/s) — pragurile. Utilizare: view.setOnTouchListener(OnSwipeTouchListener(context).apply { onSwipeLeft = { dismiss() } }).
Adaugă un buton de ștergere la swipe-ul spre stânga pe celula tabelului. Folosește API-ul încorporat iOS 11+ fără recunoaștere manuală a gesturilor.
func tableView(
_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(
style: .destructive,
title: "Șterge"
) { _, _, 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
}
Metoda returnează UISwipeActionsConfiguration cu un array de UIContextualAction. style: .destructive setează automat fundalul roșu. performsFirstActionWithFullSwipe = true permite executarea acțiunii cu un swipe complet fără apăsare suplimentară. În IT Sectr folosim acest API pentru toate listele cu acțiuni — este standardizat, suportă VoiceOver și nu necesită GestureRecognizer personalizate.
Întrebări frecvente
Swipe — gest discret cu mișcare rapidă și ridicare a degetului, panoramarea — tragere continuă cu urmărirea coordonatelor. UISwipeGestureRecognizer se activează o dată, UIPanGestureRecognizer generează .began → .changed → .ended. Alegerea depinde de scenariu: swipe — pentru acțiuni (șterge, întoarce), pan — pentru deplasare (hartă, slider).
Logica gestului este identică, dar API-ul diferă. iOS folosește UISwipeGestureRecognizer cu direction. Android folosește GestureDetector.onFling() cu praguri personalizate. Rezultatul — același UX cu implementări diferite. Framework-urile cross-platform (Flutter, React Native) abstrage diferența printr-un API unitar de swipe.
În iOS (11+) folosiți UISwipeActionsConfiguration în UITableViewDelegate. În Android — ItemTouchHelper.SimpleCallback pentru RecyclerView. Aceste API-uri înlocuiesc GestureRecognizer manuale, suportă butoane de acțiune și animație, gestionează corect conflictul cu derularea și se adaptează la HIG-urile platformei.
Concluzii
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