Главное
Свайп — это быстрое скользящее движение пальцем по поверхности экрана, завершающееся отрывом. Свайп может быть коротким (пролистывание элемента) или длинным (открытие шторки), но ключевая характеристика — дискретность: система распознаёт его как одно событие, а не как непрерывную последовательность координат.
Мобильные ОС определяют свайп по трём параметрам: минимальное расстояние (порог), минимальная скорость (velocity) и направление. Если палец движется медленно или расстояние недостаточно, система интерпретирует жест как панорамирование (pan) или тап. Пороговые значения различаются между платформами: iOS использует константу UISwipeGestureRecognizer с фиксированным порогом, Android — ViewConfiguration.getScaledTouchSlop(), возвращающий ~8–16 dp в зависимости от плотности экрана.
По данным Android Developers Documentation, onFling (свайп) в GestureDetector срабатывает при скорости выше 100 px/s и расстоянии от 50px. В IT Sectr мы используем свайп для навигации (галереи, карточные интерфейсы, онбординг) — этот жест интуитивно понятен пользователям любых возрастов.
Свайп и панорамирование (pan/drag) — разные типы жестов, хотя оба используют движение пальца по экрану. Свайп дискретен и означает «сделать одно действие», тогда как панорамирование непрерывно и означает «перемещать объект, пока палец на экране».
| Параметр | Свайп (Swipe) | Панорамирование (Pan/Drag) |
|---|---|---|
| Тип | Дискретный | Непрерывный |
| События | Одно (распознан/не распознан) | Множество (.began → .changed → .ended) |
| Скорость | Высокая (быстрое движение) | Любая (медленное перемещение) |
| Пример iOS | UISwipeGestureRecognizer | UIPanGestureRecognizer |
| Пример Android | GestureDetector.onFling() | GestureDetector.onScroll() |
| Сценарий | Удалить письмо, перелистнуть страницу | Переместить карту, перетащить файл |
| Отслеживание координат | Нет (только направление) | Да (постоянное обновление координат) |
Выбор между свайпом и панорамированием зависит от задачи. Для действий с элементом (удалить, архивировать, открыть меню) используйте свайп. Для прямого манипулирования (перемещение, масштабирование) — панорамирование. Смешивание жестов на одном элементе требует 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 на одной вью может потребоваться require(toFail:) для предотвращения ложного распознавания тапа.
В Android основной API для распознавания свайпа — GestureDetector с колбэком 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, который абстрагирует детекцию жеста, анимацию и колбэки onSwiped(). В IT Sectr мы используем кастомный SwipeListener для ViewPager2 и карточных интерфейсов — это даёт контроль над чувствительностью свайпа без ущерба для производительности.
Для списков (UITableView, RecyclerView) Apple и Google предоставляют специализированные API, которые заменяют ручное добавление UISwipeGestureRecognizer. Эти API поддерживают кнопки действий, кастомную анимацию и жесты «проведи, чтобы удалить» — стандартные паттерны мобильных приложений.
В iOS (11+) используется UISwipeActionsConfiguration: метод tableView(_:leadingSwipeActionsConfigurationForRowAt:) возвращает массив UIContextualAction — кнопок с заголовком, стилем (destructive, normal) и обработчиком. Например, «Удалить» — .destructive с красным фоном, «Архив» — .normal с серым. Система сама анимирует появление кнопок и вызывает completion-блок после нажатия.
В Android ItemTouchHelper.SimpleCallback прикрепляется к RecyclerView через ItemTouchHelper.attachToRecyclerView(). Параметры — флаги направлений (start, end, up, down) и колбэк 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 выполняется только при наличии предыдущего экрана в стеке. Для модальных контроллеров используется dismiss. Код не требует проверки state — UISwipeGestureRecognizer дискретен и вызывает action только при успешном распознавании.
Реализует простую обёртку GestureDetector, которая определяет направление свайпа и вызывает колбэки 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.
Часто задаваемые вопросы
Свайп — дискретный жест с быстрым движением и отрывом пальца, панорамирование — непрерывное перетаскивание с отслеживанием координат. UISwipeGestureRecognizer срабатывает один раз, UIPanGestureRecognizer генерирует .began → .changed → .ended. Выбор зависит от сценария: свайп — для действий (удалить, перелистнуть), pan — для перемещения (карта, слайдер).
Логика жеста идентична, но API различается. iOS использует UISwipeGestureRecognizer с direction. Android использует GestureDetector.onFling() с кастомными порогами. Результат — одинаковый UX при разной реализации. Кроссплатформенные фреймворки (Flutter, React Native) абстрагируют разницу через единый API свайпа.
В iOS (11+) используйте UISwipeActionsConfiguration в UITableViewDelegate. В Android — ItemTouchHelper.SimpleCallback для RecyclerView. Эти API заменяют ручные GestureRecognizer, поддерживают кнопки действий и анимацию, корректно обрабатывают конфликт с прокруткой и адаптируются под платформенные HIG.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также