Najważniejsze
Swipe — to szybki, ślizgowy ruch palcem po powierzchni ekranu, kończący się oderwaniem. Swipe może być krótki (przewinięcie elementu) lub długi (otwarcie przesłony), ale kluczową cechą jest dyskretność: system rozpoznaje go jako jedno zdarzenie, a nie jako ciągłą sekwencję współrzędnych.
Systemy mobilne określają swipe na podstawie trzech parametrów: minimalna odległość (próg), minimalna prędkość (velocity) i kierunek. Jeśli palec porusza się wolno lub odległość jest niewystarczająca, system interpretuje gest jako panoramowanie (pan) lub tapnięcie. Wartości progowe różnią się między platformami: iOS używa stałej UISwipeGestureRecognizer z ustalonym progiem, Android — ViewConfiguration.getScaledTouchSlop(), zwracający ~8–16 dp w zależności od gęstości ekranu.
Według danych Android Developers Documentation, onFling (swipe) w GestureDetector uruchamia się przy prędkości powyżej 100 px/s i odległości od 50px. W IT Sectr używamy swipe do nawigacji (galerie, interfejsy kart, onboarding) — ten gest jest intuicyjnie zrozumiały dla użytkowników w każdym wieku.
Swipe i panoramowanie (pan/drag) — to różne typy gestów, chociaż oba wykorzystują ruch palca po ekranie. Swipe jest dyskretny i oznacza „wykonaj jedną czynność”, podczas gdy panoramowanie jest ciągłe i oznacza „przesuwaj obiekt, dopóki palec jest na ekranie”.
| Parametr | Swipe | Panoramowanie (Pan/Drag) |
|---|---|---|
| Typ | Dyskretny | Ciągły |
| Zdarzenia | Jedno (rozpoznany/nie rozpoznany) | Wiele (.began → .changed → .ended) |
| Prędkość | Wysoka (szybki ruch) | Dowolna (wolne przesuwanie) |
| Przykład iOS | UISwipeGestureRecognizer | UIPanGestureRecognizer |
| Przykład Android | GestureDetector.onFling() | GestureDetector.onScroll() |
| Scenariusz | Usunąć e-mail, przewinąć stronę | Przesunąć mapę, przeciągnąć plik |
| Śledzenie współrzędnych | Nie (tylko kierunek) | Tak (ciągła aktualizacja współrzędnych) |
Wybór między swipe a panoramowaniem zależy od zadania. Dla działań na elemencie (usunięcie, zarchiwizowanie, otwarcie menu) używaj swipe. Dla bezpośredniego manipulowania (przesuwanie, skalowanie) — panoramowanie. Mieszanie gestów na jednym elemencie wymaga require(toFail:) lub niestandardowej logiki.
UISwipeGestureRecognizer — podklasa UIGestureRecognizer, rozpoznająca dyskretny swipe w jednym z czterech kierunków. W przeciwieństwie do UIPanGestureRecognizer, nie generuje pośrednich zdarzeń .changed: przy rozpoznaniu akcja wywoływana jest dokładnie raz.
Właściwość direction (UISwipeGestureRecognizer.Direction) określa śledzony kierunek — .left, .right, .up, .down. Można łączyć przez maskę: [.left, .right] dla poziomych przesunięć. Właściwość numberOfTouchesRequired określa liczbę palców — domyślnie 1. UISwipeGestureRecognizer nie ma konfigurowalnego progu odległości — iOS używa wbudowanej wartości około 20px, wystarczającej do niezawodnego rozpoznawania.
Ponieważ UISwipeGestureRecogniser jest dyskretnym rozpoznawaczem, nie koliduje z UIPanGestureRecognizer: przy wolnym ruchu zadziała pan, przy szybkim — swipe. Jednak przy współistnieniu z UITapGestureRecognizer na tym samym widoku może być wymagane require(toFail:) w celu zapobieżenia fałszywemu rozpoznaniu tapnięcia.
W Android głównym API do rozpoznawania swipe jest GestureDetector z callbackiem OnGestureListener, a konkretnie metoda onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY). GestureDetector analizuje początkowe i końcowe położenie palca, prędkość ruchu wzdłuż osi i odległość, po czym wywołuje onFling po przekroczeniu progów.
Minimalne progi konfiguruje się przez ViewConfiguration: VelocityTracker.getMinVelocity() (~100 px/s) i ViewConfiguration.getScaledTouchSlop() (~8–16 dp). Niestandardowy OnSwipeTouchListener — popularny wzorzec upraszczający API: programista nie musi ręcznie obliczać kierunku i prędkości. Android Support Library nie zawiera wbudowanego „SwipeDetector”, więc niestandardowa otoczka to standardowe rozwiązanie.
Dla swipe w listach Google zaleca używanie ItemTouchHelper.SimpleCallback z RecyclerView, który abstrahuje detekcję gestu, animację i callbacki onSwiped(). W IT Sectr używamy niestandardowego SwipeListener dla ViewPager2 i interfejsów kart — daje to kontrolę nad czułością swipe bez uszczerbku dla wydajności.
Dla list (UITableView, RecyclerView) Apple i Google udostępniają specjalistyczne API, które zastępują ręczne dodawanie UISwipeGestureRecognizer. Te API obsługują przyciski akcji, niestandardową animację i gesty „przesuń, aby usunąć” — standardowe wzorce aplikacji mobilnych.
W iOS (11+) używany jest UISwipeActionsConfiguration: metoda tableView(_:leadingSwipeActionsConfigurationForRowAt:) zwraca tablicę UIContextualAction — przycisków z tytułem, stylem (destructive, normal) i handlerem. Na przykład „Usuń” — .destructive z czerwonym tłem, „Archiwum” — .normal z szarym. System sam animuje pojawianie się przycisków i wywołuje blok completion po naciśnięciu.
W Android ItemTouchHelper.SimpleCallback jest dołączany do RecyclerView przez ItemTouchHelper.attachToRecyclerView(). Parametry — flagi kierunków (start, end, up, down) i callback onSwiped(viewHolder, direction). Wewnątrz onSwiped wywoływany jest adapter.notifyItemRemoved(position) w celu usunięcia elementu z animacją. ItemTouchHelper automatycznie obsługuje gesty, konflikty z przewijaniem (scroll) i zwrot elementu, jeśli swipe nie został zakończony. Według Google I/O 2024, ItemTouchHelper jest używany w 70% aplikacji Android z akcjami przesunięcia w listach.
Dodaje UISwipeGestureRecognizer do widoku w celu powrotu do poprzedniego ekranu. Kod pokazuje minimalną konfigurację rozpoznawacza z kierunkiem .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)
}
}
}
Rozpoznawacz jest dołączany do widoku głównego kontrolera. Sprawdzenie navigationController.viewControllers.count gwarantuje, że pop jest wykonywany tylko wtedy, gdy w stosie znajduje się poprzedni ekran. Dla kontrolerów modalnych używany jest dismiss. Kod nie wymaga sprawdzania stanu — UISwipeGestureRecognizer jest dyskretny i wywołuje akcję tylko przy pomyślnym rozpoznaniu.
Implementuje prostą otoczkę GestureDetector, która określa kierunek przesunięcia i wywołuje callbacki 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 rozszerza SimpleOnGestureListener, implementując onFling. diffX/diffY określają kierunek, SWIPE_THRESHOLD (100px) i SWIPE_VELOCITY_THRESHOLD (100 px/s) — progi. Użycie: view.setOnTouchListener(OnSwipeTouchListener(context).apply { onSwipeLeft = { dismiss() } }).
Dodaje przycisk usuwania przy przesunięciu w lewo po komórce tabeli. Używa wbudowanego API iOS 11+ bez ręcznego rozpoznawania gestów.
func tableView(
_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(
style: .destructive,
title: "Usuń"
) { _, _, 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 zwraca UISwipeActionsConfiguration z tablicą UIContextualAction. style: .destructive automatycznie ustawia czerwone tło. performsFirstActionWithFullSwipe = true umożliwia wykonanie akcji pełnym przesunięciem bez dodatkowego naciskania. W IT Sectr używamy tego API dla wszystkich list z akcjami — jest standaryzowane, obsługuje VoiceOver i nie wymaga niestandardowych GestureRecognizer.
Często zadawane pytania
Swipe — dyskretny gest z szybkim ruchem i oderwaniem palca, panoramowanie — ciągłe przeciąganie ze śledzeniem współrzędnych. UISwipeGestureRecognizer uruchamia się raz, UIPanGestureRecognizer generuje .began → .changed → .ended. Wybór zależy od scenariusza: swipe — dla działań (usuń, przewiń), pan — dla przesuwania (mapa, suwak).
Logika gestu jest identyczna, ale API się różni. iOS używa UISwipeGestureRecognizer z direction. Android używa GestureDetector.onFling() z niestandardowymi progami. Wynik — taki sam UX przy różnej implementacji. Frameworki wieloplatformowe (Flutter, React Native) abstrahują różnicę przez jednolite API swipe.
W iOS (11+) użyj UISwipeActionsConfiguration w UITableViewDelegate. W Android — ItemTouchHelper.SimpleCallback dla RecyclerView. Te API zastępują ręczne GestureRecognizer, obsługują przyciski akcji i animację, poprawnie obsługują konflikt z przewijaniem i dostosowują się do platformowych HIG.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również