النقاط الرئيسية
UIGestureRecognizer هي فئة أساسية مجردة في UIKit تفصل عملية التعرف على الإيماءات إلى كائنات منفصلة. كل فئة فرعية تتعامل مع نوع إيماءة واحد: UITapGestureRecognizer يتعامل مع النقرة بعدد محدد من اللمسات، UISwipeGestureRecognizer يتعامل مع التمريرة السريعة في اتجاه معين. يضيف المطور أداة التعرف إلى UIView عبر طريقة addGestureRecognizer(:)، ويقوم UIKit بتتبع اللمسات تلقائيًا، وتحديث الحالة، واستدعاء إجراء عند التعرف.
قبل 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 حالات محتملة، محددة في التعداد 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. في طريقة الإجراء، تحقق من gestureRecognizer.state — مما يسمح بالتمييز بين بداية الإيماءة وتغييرها ونهايتها.
يوفر UIKit 7 فئات فرعية مدمجة من UIGestureRecognizer، تغطي معظم سيناريوهات التفاعل. لكل فئة فرعية إعدادات محددة: numberOfTapsRequired للنقرة، direction للتمريرة السريعة، minimumPressDuration للضغطة الطويلة.
للإيماءات المخصصة (على سبيل المثال، رسم شكل متعرج)، يتم إنشاء فئة فرعية من UIGestureRecognizer مع إعادة تعريف touchesBegan وtouchesMoved وtouchesEnded وتحديث الحالة. توصي Apple باستخدام الفئات المدمجة حيثما أمكن — فهي محسّنة وتتفاعل بشكل صحيح مع بعضها البعض.
عند استخدام عدة UIGestureRecognizer على UIView واحدة (مثلاً، نقرة ونقرة مزدوجة)، يحدث تعارض في التعرف: عند النقرة المزدوجة، يتم تشغيل النقرة المفردة أولاً. توفر Apple أسلوب require(toFail:) لتأخير التعرف على إيماءة واحدة حتى تفشل الأخرى.
تعمل الآلية كالتالي: باستدعاء tapRecognizer.require(toFail: doubleTapRecognizer)، تحدد أن tapRecognizer سينتقل إلى حالة .recognized فقط بعد أن ينتهي doubleTapRecognizer في .failed. يضيف هذا تأخيرًا قدره ~0.3 ثانية قبل تنفيذ النقرة المفردة — ينقر المستخدم مرتين، ويتم تجاهل النقرة الأولى. النهج البديل هو المفوض UIGestureRecognizerDelegate مع أسلوب gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)، الذي يسمح بالتعرف المتزامن (على سبيل المثال، pan + pinch للخريطة).
وفقًا لـ WWDC 2020، حوالي 15% من الأخطاء في تطبيقات UIKit مرتبطة بالتكوين غير الصحيح لتعارضات الإيماءات. في IT Sectr، قمنا بتوحيد النهج: كل شاشة لها مخطط إيماءات مع أولويات require(toFail:) — مما قضى تمامًا على أخطاء التشغيل المزدوج للنقرات.
يضيف معالج نقرة مفردة إلى 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، هذه العلامة مفعلة افتراضيًا. تقبل طريقة الإجراء معامل 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 هي إيماءة منفصلة: تنتقل إلى .recognized فورًا بعد التعرف، دون حالات .changed وسيطة. لذلك، ليست هناك حاجة للتحقق من الحالة في الإجراء — إما يتم التعرف على الإيماءة (يتم تشغيل الإجراء) أو لا. الخاصية 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 — مما يفسد تجربة المستخدم. مع require(toFail:)، تنتظر النقرة المفردة ~0.3 ثانية للتأكد من عدم وجود نقرة ثانية. في IT Sectr، يُستخدم هذا النمط في محررات الصور والمعارض، حيث تقوم النقرة المزدوجة بالتكبير والنقرة المفردة بتحديد عنصر.
الأسئلة الشائعة
نعم، يدعم UIView مثيلات متعددة من UIGestureRecognizer في وقت واحد. لحل النزاعات، استخدم أسلوب require(toFail:) الذي يحدد ترتيب التعرف. للتشغيل المتوازي للإيماءات (على سبيل المثال، pan + pinch على خريطة)، قم بتنفيذ أسلوب المفوض gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) مع إرجاع true.
UIGestureRecognizer هو تجريد عالي المستوى يتعرف تلقائيًا على أنماط اللمس ويدير الحالات. touchesBegan هو أسلوب منخفض المستوى من UIResponder يتطلب تتبعًا يدويًا للإحداثيات والتوقيت وإلغاء اللمس. Gesture Recognizer أبسط وأكثر موثوقية ومفضل للإيماءات القياسية؛ touchesBegan مبرر فقط للرسومات المخصصة.
يستخدم SwiftUI الأصلي معدّلات: onTapGesture، onLongPressGesture، DragGesture، MagnificationGesture، RotationGesture. هذه نظائر تصريحية لـ UIGestureRecognizer مدمجة في هرمية SwiftUI. إذا لزم الأمر، يمكن تغليف أداة تعرف UIKit عبر UIViewRepresentable، لكن Apple توصي باستخدام إيماءات SwiftUI الأصلية.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.