Βασικά σημεία
Swipe — μια γρήγορη ολισθαίνουσα κίνηση του δαχτύλου στην επιφάνεια της οθόνης που τελειώνει με απομάκρυνση. Η σάρωση μπορεί να είναι σύντομη (κύλιση στοιχείου) ή μεγάλη (άνοιγμα κουρτίνας), αλλά το βασικό χαρακτηριστικό είναι η διακριτότητα: το σύστημα την αναγνωρίζει ως ένα γεγονός, όχι ως συνεχή ακολουθία συντεταγμένων.
Τα κινητά ΛΣ καθορίζουν τη σάρωση με τρεις παραμέτρους: ελάχιστη απόσταση (όριο), ελάχιστη ταχύτητα (velocity) και κατεύθυνση. Αν το δάχτυλο κινείται αργά ή η απόσταση δεν είναι επαρκής, το σύστημα ερμηνεύει τη χειρονομία ως μετατόπιση (pan) ή πάτημα. Οι τιμές ορίων διαφέρουν μεταξύ πλατφορμών: το iOS χρησιμοποιεί τη σταθερά UISwipeGestureRecognizer με σταθερό όριο, το Android — ViewConfiguration.getScaledTouchSlop(), που επιστρέφει ~8–16 dp ανάλογα με την πυκνότητα οθόνης.
Σύμφωνα με το Android Developers Documentation, το onFling (swipe) στο GestureDetector ενεργοποιείται σε ταχύτητα άνω των 100 px/s και απόσταση από 50px. Στην IT Sectr χρησιμοποιούμε τη σάρωση για πλοήγηση (γκάλερι, διεπαφές καρτών, onboarding) — αυτή η χειρονομία είναι διαισθητική για χρήστες όλων των ηλικιών.
Swipe και μετατόπιση (pan/drag) είναι διαφορετικοί τύποι χειρονομιών, αν και και οι δύο χρησιμοποιούν κίνηση του δαχτύλου στην οθόνη. Η σάρωση είναι διακριτή και σημαίνει „κάνε μία ενέργεια”, ενώ η μετατόπιση είναι συνεχής και σημαίνει „μετακίνησε το αντικείμενο όσο το δάχτυλο είναι στην οθόνη”.
| Παράμετρος | Swipe | Μετατόπιση (Pan/Drag) |
|---|---|---|
| Τύπος | Διακριτός | Συνεχής |
| Γεγονότα | Ένα (αναγνωρίστηκε/δεν αναγνωρίστηκε) | Πολλαπλά (.began → .changed → .ended) |
| Ταχύτητα | Υψηλή (γρήγορη κίνηση) | Οποιαδήποτε (αργή κίνηση) |
| Παράδειγμα iOS | UISwipeGestureRecognizer | UIPanGestureRecognizer |
| Παράδειγμα Android | GestureDetector.onFling() | GestureDetector.onScroll() |
| Σενάριο | Διαγραφή email, γύρισμα σελίδας | Μετακίνηση χάρτη, σύρσιμο αρχείου |
| Παρακολούθηση συντεταγμένων | Όχι (μόνο κατεύθυνση) | Ναι (συνεχής ενημέρωση συντεταγμένων) |
Η επιλογή μεταξύ σάρωσης και μετατόπισης εξαρτάται από την εργασία. Για ενέργειες στο στοιχείο (διαγραφή, αρχειοθέτηση, άνοιγμα μενού) χρησιμοποιήστε σάρωση. Για άμεσο χειρισμό (μετακίνηση, κλίμακα) — μετατόπιση. Η ανάμειξη χειρονομιών στο ίδιο στοιχείο απαιτεί require(toFail:) ή προσαρμοσμένη λογική.
UISwipeGestureRecognizer — μια υποκλάση του UIGestureRecognizer που αναγνωρίζει διακριτή σάρωση σε μία από τέσσερις κατευθύνσεις. Σε αντίθεση με το UIPanGestureRecognizer, δεν δημιουργεί ενδιάμεσα γεγονότα .changed: κατά την αναγνώριση, το action καλείται ακριβώς μία φορά.
Η ιδιότητα direction (UISwipeGestureRecognizer.Direction) ορίζει την παρακολουθούμενη κατεύθυνση — .left, .right, .up, .down. Μπορεί να συνδυαστεί μέσω μάσκας: [.left, .right] για οριζόντιες σαρώσεις. Η ιδιότητα numberOfTouchesRequired καθορίζει τον αριθμό δαχτύλων — προεπιλογή 1. Το UISwipeGestureRecognizer δεν έχει ρυθμιζόμενο όριο απόστασης — το iOS χρησιμοποιεί ενσωματωμένη τιμή περίπου 20px, επαρκή για αξιόπιστη αναγνώριση.
Δεδομένου ότι το UISwipeGestureRecogniser είναι διακριτός αναγνωριστής, δεν συγκρούεται με το UIPanGestureRecognizer: σε αργή κίνηση ενεργοποιείται το pan, σε γρήγορη — το swipe. Ωστόσο, όταν συνυπάρχει με UITapGestureRecognizer στο ίδιο view, μπορεί να απαιτηθεί require(toFail:) για την αποφυγή εσφαλμένης αναγνώρισης πατήματος.
Στο Android, το κύριο API για αναγνώριση σάρωσης είναι το GestureDetector με callback OnGestureListener, συγκεκριμένα η μέθοδος onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY). Το GestureDetector αναλύει την αρχική και τελική θέση του δαχτύλου, την ταχύτητα κίνησης κατά άξονες και την απόσταση, και στη συνέχεια καλεί το onFling όταν υπερβαίνει τα όρια.
Τα ελάχιστα όρια ρυθμίζονται μέσω ViewConfiguration: VelocityTracker.getMinVelocity() (~100 px/s) και ViewConfiguration.getScaledTouchSlop() (~8–16 dp). Το προσαρμοσμένο OnSwipeTouchListener είναι ένα διαδεδομένο μοτίβο που απλοποιεί το API: ο προγραμματιστής δεν χρειάζεται να υπολογίζει χειροκίνητα την κατεύθυνση και την ταχύτητα. Η Android Support Library δεν περιέχει ενσωματωμένο „SwipeDetector”, επομένως το προσαρμοσμένο περιτύλιγμα είναι η τυπική λύση.
Για σάρωση σε λίστες, η Google συνιστά τη χρήση ItemTouchHelper.SimpleCallback από το RecyclerView, το οποίο αφαιρεί την ανίχνευση χειρονομίας, την κίνηση και τα callbacks onSwiped(). Στην IT Sectr χρησιμοποιούμε προσαρμοσμένο SwipeListener για ViewPager2 και διεπαφές καρτών — αυτό παρέχει έλεγχο της ευαισθησίας σάρωσης χωρίς επιβάρυνση της απόδοσης.
Για λίστες (UITableView, RecyclerView) η Apple και η Google παρέχουν εξειδικευμένα API που αντικαθιστούν τη μη αυτόματη προσθήκη UISwipeGestureRecognizer. Αυτά τα API υποστηρίζουν κουμπιά ενεργειών, προσαρμοσμένη κίνηση και χειρονομίες „σύρετε για διαγραφή” — τυπικά μοτίβα εφαρμογών κινητών.
Στο iOS (11+) χρησιμοποιείται UISwipeActionsConfiguration: η μέθοδος tableView(_:leadingSwipeActionsConfigurationForRowAt:) επιστρέφει έναν πίνακα UIContextualAction — κουμπιά με τίτλο, στυλ (destructive, normal) και χειριστή. Για παράδειγμα, „Διαγραφή” — .destructive με κόκκινο φόντο, „Αρχειοθέτηση” — .normal με γκρι. Το σύστημα κινεί αυτόματα την εμφάνιση των κουμπιών και καλεί το completion block μετά το πάτημα.
Στο Android, το ItemTouchHelper.SimpleCallback συνδέεται στο RecyclerView μέσω ItemTouchHelper.attachToRecyclerView(). Παράμετροι — σημαίες κατεύθυνσης (start, end, up, down) και callback onSwiped(viewHolder, direction). Μέσα στο onSwiped καλείται adapter.notifyItemRemoved(position) για διαγραφή στοιχείου με κίνηση. Το ItemTouchHelper χειρίζεται αυτόματα χειρονομίες, συγκρούσεις με κύλιση (scroll) και επιστροφή στοιχείου αν η σάρωση δεν ολοκληρωθεί. Σύμφωνα με το Google I/O 2024, το ItemTouchHelper χρησιμοποιείται στο 70% των εφαρμογών Android με ενέργειες σάρωσης σε λίστες.
Προσθέτει UISwipeGestureRecognizer στο view για επιστροφή στην προηγούμενη οθόνη. Ο κώδικας δείχνει την ελάχιστη διαμόρφωση του αναγνωριστή με κατεύθυνση .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)
}
}
}
Ο αναγνωριστής συνδέεται στο root-view του ελεγκτή. Ο έλεγχος navigationController.viewControllers.count εγγυάται ότι το pop εκτελείται μόνο όταν υπάρχει προηγούμενη οθόνη στη στοίβα. Για modal ελεγκτές χρησιμοποιείται dismiss. Ο κώδικας δεν απαιτεί έλεγχο κατάστασης — το UISwipeGestureRecognizer είναι διακριτό και καλεί το action μόνο σε επιτυχή αναγνώριση.
Υλοποιεί ένα απλό περιτύλιγμα GestureDetector που καθορίζει την κατεύθυνση σάρωσης και καλεί τα callbacks 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 επεκτείνει το SimpleOnGestureListener, υλοποιώντας το onFling. Τα diffX/diffY καθορίζουν την κατεύθυνση, SWIPE_THRESHOLD (100px) και SWIPE_VELOCITY_THRESHOLD (100 px/s) — όρια. Χρήση: view.setOnTouchListener(OnSwipeTouchListener(context).apply { onSwipeLeft = { dismiss() } }).
Προσθέτει κουμπί διαγραφής κατά τη σάρωση αριστερά στο κελί πίνακα. Χρησιμοποιεί ενσωματωμένο API iOS 11+ χωρίς μη αυτόματη αναγνώριση χειρονομιών.
func tableView(
_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(
style: .destructive,
title: "Διαγραφή"
) { _, _, 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
}
Η μέθοδος επιστρέφει UISwipeActionsConfiguration με πίνακα UIContextualAction. style: .destructive ορίζει αυτόματα κόκκινο φόντο. performsFirstActionWithFullSwipe = true επιτρέπει την εκτέλεση ενέργειας με πλήρη σάρωση χωρίς πρόσθετο πάτημα. Στην IT Sectr χρησιμοποιούμε αυτό το API για όλες τις λίστες με ενέργειες — είναι τυποποιημένο, υποστηρίζει VoiceOver και δεν απαιτεί προσαρμοσμένα GestureRecognizer.
Συχνές ερωτήσεις
Swipe — διακριτή χειρονομία με γρήγορη κίνηση και απομάκρυνση δαχτύλου, μετατόπιση — συνεχές σύρσιμο με παρακολούθηση συντεταγμένων. Το UISwipeGestureRecognizer ενεργοποιείται μία φορά, το UIPanGestureRecognizer δημιουργεί .began → .changed → .ended. Η επιλογή εξαρτάται από το σενάριο: swipe — για ενέργειες (διαγραφή, γύρισμα), pan — για μετακίνηση (χάρτης, ρυθμιστικό).
Η λογική της χειρονομίας είναι ταυτόσημη, αλλά το API διαφέρει. Το iOS χρησιμοποιεί UISwipeGestureRecognizer με direction. Το Android χρησιμοποιεί GestureDetector.onFling() με προσαρμοσμένα όρια. Το αποτέλεσμα — ίδιο UX με διαφορετική υλοποίηση. Τα cross-platform frameworks (Flutter, React Native) αφαιρούν τη διαφορά μέσω ενιαίου API σάρωσης.
Στο iOS (11+) χρησιμοποιήστε UISwipeActionsConfiguration στο UITableViewDelegate. Στο Android — ItemTouchHelper.SimpleCallback για RecyclerView. Αυτά τα API αντικαθιστούν τα μη αυτόματα GestureRecognizer, υποστηρίζουν κουμπιά ενεργειών και κίνηση, χειρίζονται σωστά τη σύγκρουση με κύλιση και προσαρμόζονται στα HIG πλατφόρμας.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης