نکات اصلی
UIGestureRecognizer — کلاس پایه انتزاعی در UIKit است که فرآیند تشخیص ژست را به اشیاء جداگانه تجزیه میکند. هر زیرکلاس مسئول یک نوع ژست است: UITapGestureRecognizer — برای ضربه با تعداد مشخصی انگشت، UISwipeGestureRecognizer — برای کشیدن در جهت مشخص. توسعهدهنده تشخیصدهنده را از طریق متد addGestureRecognizer(:) به UIView اضافه میکند و UIKit به طور خودکار لمسها را ردیابی میکند، وضعیت را بهروز میکند و در صورت تشخیص، action را فراخوانی میکند.
قبل از ظهور UIGestureRecognizer (iOS 3.2, 2010) توسعهدهندگان متدهای UIResponder — touchesBegan, touchesMoved, touchesEnded — را بازنویسی میکردند و به صورت دستی مسیر لمسها را تحلیل میکردند. این منجر به تکرار کد و خطاها در پردازش لمسهای همزمان میشد. Apple این منطق را در UIGestureRecognizer کپسوله کرد و پشتیبانی از مولتیتاچ، لغو ژستها و کار همزمان چندین تشخیصدهنده روی یک ویو را اضافه کرد.
طبق جلسات WWDC، Gesture Recognizer تا 11 لمس همزمان در iPad و 5 در iPhone را پردازش میکند. در IT Sectr ما از UIGestureRecognizer به عنوان روش استاندارد پردازش ورودی کاربر در تمام پروژههای UIKit استفاده میکنیم — این کار باگهای مربوط به ردیابی دستی touchesBegan را حذف کرده است.
هر UIGestureRecognizer از 7 وضعیت ممکن عبور میکند که در enum UIGestureRecognizer.State تعریف شدهاند. این وضعیتها چرخه حیات تشخیص را منعکس میکنند: از تشخیص لمس تا تکمیل یا لغو ژست. درک وضعیتها برای پیادهسازی تشخیصدهندههای سفارشی و رفع اشکال تعارضات حیاتی است.
| وضعیت | معنی | زمان وقوع |
|---|---|---|
| .possible | وضعیت اولیه، ژست هنوز تشخیص داده نشده | بلافاصله پس از اضافه شدن به ویو |
| .began | ژست تشخیص داده شد و شروع به اجرا شد | در اولین حرکت انگشت برای pan/longPress |
| .changed | پارامترهای ژست تغییر کردند (مختصات، زاویه) | در هر حرکت انگشت |
| .ended | کاربر انگشت را برداشت، ژست کامل شد | در touchesEnded |
| .cancelled | ژست توسط سیستم قطع شد (تماس ورودی، تغییر جهت) | در touchesCancelled |
| .failed | ژست با توجه به شرایط تشخیص داده نشد | در touchesCancelled بدون تشخیص |
| .recognized | مترادف .ended; ژست با موفقیت تشخیص داده شد | همان .ended |
ژستهای گسسته (tap, swipe) از .possible مستقیماً به .ended یا .failed میروند. ژستهای پیوسته (pan, pinch, rotation, longPress) از .possible → .began → .changed (چندبار) → .ended عبور میکنند. در متد action، gestureRecognizer.state را بررسی کنید — این امکان تشخیص شروع، تغییر و پایان ژست را میدهد.
UIKit 7 زیرکلاس داخلی UIGestureRecognizer را ارائه میدهد که اکثر سناریوهای تعامل را پوشش میدهد. هر زیرکلاس تنظیمات خاصی دارد: numberOfTapsRequired برای ضربه، direction برای کشیدن، minimumPressDuration برای فشار طولانی.
برای ژستهای سفارشی (مثلاً رسم زیگزاگ) زیرکلاسی از UIGestureRecognizer با بازنویسی متدهای touchesBegan, touchesMoved, touchesEnded و بهروزرسانی state ایجاد میشود. Apple توصیه میکند تا حد امکان از کلاسهای داخلی استفاده کنید — آنها بهینه شدهاند و به درستی با یکدیگر تعامل دارند.
وقتی روی یک UIView از چند UIGestureRecognizer استفاده میشود (مثلاً tap و double-tap)، تعارض تشخیص رخ میدهد: در ضربه دوبل، ابتدا ضربه تکی فعال میشود. برای حل این مشکل، Apple متد require(toFail:) را ارائه میدهد که تشخیص یک ژست را تا زمانی که ژست دیگر با شکست مواجه شود، به تأخیر میاندازد.
مکانیزم به این صورت کار میکند: با فراخوانی tapRecognizer.require(toFail: doubleTapRecognizer)، مشخص میکنید که tapRecogniser فقط پس از آنکه doubleTapRecogniser در .failed تمام شود، به وضعیت .recognized میرود. این یک تأخیر ~0.3 ثانیه قبل از اجرای ضربه تکی اضافه میکند — کاربر دو بار ضربه میزند و اولین ضربه نادیده گرفته میشود. روش جایگزین — delegate UIGestureRecognizerDelegate با متد gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) که تشخیص همزمان را مجاز میکند (مثلاً pan + pinch برای نقشه).
طبق WWDC 2020، حدود 15٪ از باگها در برنامههای UIKit به پیکربندی نادرست تعارضات ژست مرتبط هستند. در IT Sectr ما رویکرد را استاندارد کردهایم: برای هر صفحه یک طرح ژست با اولویتهای require(toFail:) ایجاد میشود — این کار باگهای فعال شدن دوبل ضربهها را کاملاً حذف کرده است.
یک handler ضربه تکی روی UIImageView اضافه میکند. هنگام ضربه، تصویر شفافیت خود را تغییر میدهد — سادهترین مثال که اتصال ژست به ویو را نشان میدهد.
import UIKit
class ImageViewController: UIViewController {
@IBOutlet private var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(
target: self,
action: #selector(handleTap(_:))
)
tap.numberOfTapsRequired = 1
imageView.addGestureRecognizer(tap)
imageView.isUserInteractionEnabled = true
}
@objc private func handleTap(_: UITapGestureRecognizer) {
UIView.animate(withDuration: 0.2) {
self.imageView.alpha = self.imageView.alpha == 1.0 ? 0.5 : 1.0
}
}
}
نکته کلیدی: isUserInteractionEnabled در UIImageView به طور پیشفرض false است — بدون این پرچم Gesture Recognizer لمسها را دریافت نمیکند. برای UIView و UIButton این پرچم به طور پیشفرض فعال است. متد action پارامتر UITapGestureRecognizer را میپذیرد که از طریق آن میتوان location(in:) را برای تعیین مختصات ضربه دریافت کرد.
کشیدن به چپ برای بازگشت به صفحه قبلی را پیادهسازی میکند. تنظیم جهت و اتصال ژست به ویو ریشه کنترلر را نشان میدهد.
import UIKit
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let swipeLeft = UISwipeGestureRecognizer(
target: self,
action: #selector(handleSwipe(_:))
)
swipeLeft.direction = .left
view.addGestureRecognizer(swipeLeft)
}
@objc private func handleSwipe(_: UISwipeGestureRecognizer) {
navigationController?.popViewController(animated: true)
}
}
UISwipeGestureRecognizer — یک ژست گسسته است: بلافاصله پس از تشخیص به .recognised میرود، بدون .changed میانی. بنابراین در action نیازی به بررسی state نیست — ژست یا تشخیص داده میشود (action فراخوانی میشود) یا نه. ویژگی direction یکی از چهار مقدار را میپذیرد: .left, .right, .up, .down. برای پشتیبانی از چندین جهت، تشخیصدهندههای جداگانه برای هر کدام ایجاد کنید.
نشان میدهد چگونه ضربه تکی و دوبل را روی یک ویو بدون تعارض تنظیم کنید. تشخیصدهنده ضربه دوبل اولویت دارد — ضربه تکی فقط در صورتی فعال میشود که ضربه دوبل تشخیص داده نشود.
import UIKit
class TapViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let singleTap = UITapGestureRecognizer(
target: self,
action: #selector(handleSingleTap)
)
singleTap.numberOfTapsRequired = 1
let doubleTap = UITapGestureRecognizer(
target: self,
action: #selector(handleDoubleTap)
)
doubleTap.numberOfTapsRequired = 2
singleTap.require(toFail: doubleTap)
view.addGestureRecognizer(singleTap)
view.addGestureRecognizer(doubleTap)
}
@objc private func handleSingleTap() {
print("Single tap — after 0.3s delay")
}
@objc private func handleDoubleTap() {
print("Double tap — instant")
}
}
بدون require(toFail:) در ضربه دوبل، ابتدا handleSingleTap و سپس handleDoubleTap فراخوانی میشود — این UX را خراب میکند. با require(toFail:) ضربه تکی ~0.3 ثانیه صبر میکند تا مطمئن شود ضربه دوم نمیآید. در IT Sectr این الگو در ویرایشگرهای تصویر و گالریها استفاده میشود، جایی که ضربه دوبل بزرگنمایی میکند و ضربه تکی عنصر را انتخاب میکند.
سوالات متداول
بله، UIView همزمان از چندین UIGestureRecognizer پشتیبانی میکند. برای حل تعارضات از متد require(toFail:) استفاده کنید که ترتیب تشخیص را تعیین میکند. برای کار موازی ژستها (مثلاً pan + pinch روی نقشه) delegate gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) را پیادهسازی کنید که true برمیگرداند.
UIGestureRecognizer — انتزاع سطح بالا که الگوهای لمس را به طور خودکار تشخیص میدهد و وضعیتها را مدیریت میکند. touchesBegan — متد سطح پایین UIResponder که نیاز به ردیابی دستی مختصات، زمانبندی و لغو لمسها دارد. Gesture Recognizer سادهتر، قابل اعتمادتر و برای ژستهای استاندارد ترجیح داده میشود؛ touchesBegan فقط برای گرافیک سفارشی توجیه دارد.
در SwiftUI بومی از اصلاحکنندهها استفاده میشود: onTapGesture, onLongPressGesture, DragGesture, MagnificationGesture, RotationGesture. اینها معادلهای اعلامی UIGestureRecognizer هستند که در سلسلهمراتب SwiftUI ادغام شدهاند. در صورت نیاز میتوان تشخیصدهنده UIKit را از طریق UIViewRepresentable پیچید، اما Apple استفاده از ژستهای بومی SwiftUI را توصیه میکند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید