نکات اصلی
سوایپ — حرکت سریع لغزشی انگشت روی سطح صفحه است که با جدا شدن پایان مییابد. سوایپ میتواند کوتاه (پیمایش عنصر) یا بلند (باز کردن پرده) باشد، اما ویژگی کلیدی — گسستگی: سیستم آن را به عنوان یک رویداد واحد تشخیص میدهد، نه دنبالهای پیوسته از مختصات.
سیستمعاملهای موبایل سوایپ را بر اساس سه پارامتر تعیین میکنند: حداقل فاصله (آستانه)، حداقل سرعت (velocity) و جهت. اگر انگشت به آرامی حرکت کند یا فاصله کافی نباشد، سیستم ژست را به عنوان پانوراما (pan) یا ضربه (tap) تفسیر میکند. مقادیر آستانه بین پلتفرمها متفاوت است: 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 تعداد انگشتان را تعیین میکند — پیشفرض ۱. UISwipeGestureRecognizer آستانه فاصله قابل تنظیمی ندارد — iOS از مقدار داخلی حدود 20px استفاده میکند که برای تشخیص قابل اطمینان کافی است.
از آنجایی که UISwipeGestureRecogniser یک تشخیصدهنده گسسته است، با UIPanGestureRecognizer تداخل ندارد: در حرکت آهسته pan فعال میشود، در حرکت سریع — swipe. با این حال در همزیستی با UITapGestureRecognizer روی یک view ممکن است برای جلوگیری از تشخیص نادرست ضربه، 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 از طریق ItemTouchHelper.attachToRecyclerView() به RecyclerView متصل میشود. پارامترها — پرچمهای جهت (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 از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید