Key Takeaways
Swipe is a quick sliding movement of a finger across the screen surface, ending with a lift-off. A swipe can be short (flipping through an element) or long (opening a curtain), but the key characteristic is discreteness: the system recognizes it as a single event, not a continuous sequence of coordinates.
Mobile OSes detect a swipe based on three parameters: minimum distance (threshold), minimum speed (velocity), and direction. If the finger moves slowly or the distance is insufficient, the system interprets the gesture as panning or a tap. Threshold values differ between platforms: iOS uses a constant in UISwipeGestureRecognizer with a fixed threshold, Android uses ViewConfiguration.getScaledTouchSlop(), which returns ~8–16 dp depending on screen density.
According to Android Developers Documentation, onFling (swipe) in GestureDetector fires at speeds above 100 px/s and distances from 50px. At IT Sectr, we use swipe for navigation (galleries, card interfaces, onboarding) — this gesture is intuitive for users of all ages.
Swipe and panning (pan/drag) are different types of gestures, although both involve moving a finger across the screen. Swipe is discrete and means "perform one action," while panning is continuous and means "move an object as long as the finger is on the screen."
| Parameter | Swipe | Pan/Drag |
|---|---|---|
| Type | Discrete | Continuous |
| Events | Single (recognized/not recognized) | Multiple (.began → .changed → .ended) |
| Speed | High (fast movement) | Any (slow movement) |
| iOS Example | UISwipeGestureRecognizer | UIPanGestureRecognizer |
| Android Example | GestureDetector.onFling() | GestureDetector.onScroll() |
| Scenario | Delete an email, flip a page | Move a map, drag a file |
| Coordinate Tracking | No (direction only) | Yes (constant coordinate updates) |
The choice between swipe and panning depends on the task. For actions on an element (delete, archive, open menu) use swipe. For direct manipulation (moving, scaling) use panning. Mixing gestures on the same element requires require(toFail:) or custom logic.
UISwipeGestureRecognizer is a subclass of UIGestureRecognizer that recognizes a discrete swipe in one of four directions. Unlike UIPanGestureRecognizer, it does not generate intermediate .changed events: when recognized, the action is called exactly once.
The direction property (UISwipeGestureRecognizer.Direction) sets the tracked direction — .left, .right, .up, .down. Directions can be combined using a mask: [.left, .right] for horizontal swipes. The numberOfTouchesRequired property determines the number of fingers — 1 by default. UISwipeGestureRecognizer does not have a configurable distance threshold — iOS uses a built-in value of about 20px, sufficient for reliable recognition.
Since UISwipeGestureRecognizer is a discrete recognizer, it does not conflict with UIPanGestureRecognizer: slow movement triggers pan, fast movement triggers swipe. However, when coexisting with UITapGestureRecognizer on the same view, require(toFail:) may be needed to prevent false tap recognition.
In Android, the primary API for swipe recognition is GestureDetector with the OnGestureListener callback, specifically the onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) method. GestureDetector analyzes the initial and final finger position, movement speed along axes, and distance, then calls onFling when thresholds are exceeded.
Minimum thresholds are configurable via ViewConfiguration: VelocityTracker.getMinVelocity() (~100 px/s) and ViewConfiguration.getScaledTouchSlop() (~8–16 dp). A custom OnSwipeTouchListener is a common pattern that simplifies the API: the developer does not need to manually calculate direction and speed. The Android Support Library does not include a built-in "SwipeDetector," so a custom wrapper is the standard solution.
For swiping in lists, Google recommends using ItemTouchHelper.SimpleCallback from RecyclerView, which abstracts gesture detection, animation, and onSwiped() callbacks. At IT Sectr, we use a custom SwipeListener for ViewPager2 and card interfaces — this gives control over swipe sensitivity without sacrificing performance.
For lists (UITableView, RecyclerView), Apple and Google provide specialized APIs that replace manually adding UISwipeGestureRecognizer. These APIs support action buttons, custom animation, and "swipe to delete" gestures — standard patterns in mobile applications.
In iOS (11+), UISwipeActionsConfiguration is used: the tableView(_:leadingSwipeActionsConfigurationForRowAt:) method returns an array of UIContextualAction — buttons with a title, style (destructive, normal), and handler. For example, "Delete" — .destructive with a red background, "Archive" — .normal with a gray background. The system animates button appearance and calls the completion block after a tap.
In Android, ItemTouchHelper.SimpleCallback is attached to RecyclerView via ItemTouchHelper.attachToRecyclerView(). Parameters include direction flags (start, end, up, down) and the onSwiped(viewHolder, direction) callback. Inside onSwiped, adapter.notifyItemRemoved(position) is called to remove the item with animation. ItemTouchHelper automatically handles gestures, scroll conflicts, and item return if the swipe is not completed. According to Google I/O 2024, ItemTouchHelper is used in 70% of Android apps with swipe actions in lists.
Adds UISwipeGestureRecognizer to a view for returning to the previous screen. The code shows the minimum recognizer configuration with the .left direction.
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)
}
}
}
The recognizer is attached to the controller's root view. The navigationController.viewControllers.count check ensures pop is only performed when there is a previous screen in the stack. For modal controllers, dismiss is used. The code does not require state checking — UISwipeGestureRecognizer is discrete and calls the action only on successful recognition.
Implements a simple GestureDetector wrapper that determines the swipe direction and calls onSwipeLeft/Right callbacks.
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 extends SimpleOnGestureListener, implementing onFling. diffX/diffY determine the direction, SWIPE_THRESHOLD (100px) and SWIPE_VELOCITY_THRESHOLD (100 px/s) are the thresholds. Usage: view.setOnTouchListener(OnSwipeTouchListener(context).apply { onSwipeLeft = { dismiss() } }).
Adds a delete button when swiping left on a table cell. Uses the built-in iOS 11+ API without manual gesture recognition.
func tableView(
_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(
style: .destructive,
title: "Delete"
) { _, _, 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
}
The method returns UISwipeActionsConfiguration with an array of UIContextualAction. style: .destructive automatically sets the red background. performsFirstActionWithFullSwipe = true allows executing the action with a full swipe without an additional tap. At IT Sectr, we use this API for all lists with actions — it is standardized, supports VoiceOver, and does not require custom GestureRecognizers.
Frequently Asked Questions
Swipe is a discrete gesture with fast movement and finger lift-off, panning is continuous dragging with coordinate tracking. UISwipeGestureRecognizer fires once, UIPanGestureRecognizer generates .began → .changed → .ended. The choice depends on the scenario: swipe is for actions (delete, flip), pan is for movement (map, slider).
The gesture logic is identical, but the API differs. iOS uses UISwipeGestureRecognizer with direction. Android uses GestureDetector.onFling() with custom thresholds. The result is the same UX with different implementations. Cross-platform frameworks (Flutter, React Native) abstract the difference through a unified swipe API.
In iOS (11+), use UISwipeActionsConfiguration in UITableViewDelegate. In Android, use ItemTouchHelper.SimpleCallback for RecyclerView. These APIs replace manual GestureRecognizers, support action buttons and animation, correctly handle scroll conflicts, and adapt to platform HIGs.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also