اختر SwiftUI Canvas عندما يتطلب السحب قواعد مواضع صريحة يمكنك تنفيذها بنفسك. اختر SpriteKit عندما ينبغي للأشكال أن تدفع بعضها أو ترتد أو تواصل الحركة بفعل الفيزياء. ابدأ بتحديد ما يحدث عندما يلتقي شكل يُسحب بشكل آخر.
يتناول هذا الدليل مهمة واحدة: اختيار بنية التفاعل لتطبيق Mac يحتوي على دوائر أو مستطيلات افتراضية في الذاكرة، يسحبها المستخدمون داخل مساحة عمل يملكها التطبيق. يعتمد القرار على سلوك التصادم، وتوجيه الإدخال، والمكوّن الذي يتحكم في موضع كل شكل.
ماذا ينبغي أن يحدث عندما يلتقي شكلان؟
قد يصف «استجابة التصادم» متطلبات مختلفة. فتمييز التداخل، أو رفض موضع غير صالح، أو جعل جسمين يرتدان، يتطلب آليات مختلفة. وتفصل Apple صراحةً بين التلامسات في SpriteKit، التي تُبلغ عن تلامس الأجسام، والتصادمات، التي تحسب استجابة فيزيائية وتطبق نبضات. About Collisions and Contacts
| السلوك المطلوب | نقطة بداية عملية | المسؤولية التي تبقى عليك |
|---|---|---|
| تمييز التداخل بينما يضع المؤشر الشكل بحرية | Canvas مع اختبارات هندسية مخصصة | تحديد العنصر، واكتشاف التداخل، والتغذية الراجعة المرئية |
| إيقاف الشكل عند أول عائق | Canvas مع قيود حركة مخصصة | اختبار مسار الحركة وتحديد الموضع المقبول |
| دفع الأشكال المجاورة والسماح بالاستقرار أو الارتداد | SpriteKit | إعداد الفيزياء ووحدة التحكم بالسحب |
| منح كل عنصر عناصر تحكم وإيماءات SwiftUI اعتيادية | عروض SwiftUI منفردة | قواعد خاصة بالتطبيق للتداخل أو الحركة |
هذه توصيات بنيوية مستمدة من القدرات الموثقة، وليست عتبات أداء حددتها Apple. لا يعني العدد القليل من الأشكال تلقائيًا أن Canvas هو الأنسب، كما لا يعني العدد الكبير أن SpriteKit مطلوب بالضرورة. اختر السلوك أولًا، ثم قِس التنفيذ باستخدام عدد ممثل من العناصر.
وثمة قيد تفاعلي مفيد أيضًا: قد يتعارض التتبع الدقيق للمؤشر مع منع التداخل منعًا صارمًا. إذا مر المؤشر عبر موضع مشغول، فعلى التطبيق أن يسمح بتداخل العنصر، أو يوقفه خلف المؤشر، أو يحرّك العناصر الأخرى. حدّد هذا الخيار قبل اختيار أداة الرسم.
ما الذي يقدمه Canvas، وما الذي عليك بناؤه؟
يوفر Canvas الرسم بنمط فوري عبر سياق رسومي. ولا تتحول دوائره ومساراته ورموزه إلى عناصر تفاعلية مستقلة. وتذكر Apple أيضًا أن Canvas لا يوفر إمكانية الوصول للعناصر المرسومة كلٌّ على حدة، بما في ذلك الرموز المقدمة كعروض. لذا يحتاج الإيماء المرفق بـ Canvas إلى منطق تطبيقي يحدد العنصر الذي اختاره المستخدم. Canvas
في محرر ذي قواعد مواضع صريحة، احتفظ بالأشكال في نموذج وتعامل مع الرسم بوصفه تمثيلًا لذلك النموذج. يمكن أن يحتوي كل عنصر على معرّف ومركز وأبعاد وترتيب رسم. توصي إرشادات Swift باستخدام البنى افتراضيًا للبيانات العادية، والفئات عندما تكون الهوية المشتركة أو وراثة الأطر مطلوبة. يخدم نموذج العناصر ذي نوع القيمة والفئة الفرعية للمشهد في SpriteKit غرضين مختلفين. Choosing Between Structures and Classes
يمكن أن يتبع السحب في Canvas التسلسل التالي:
- عند بدء الإيماءة، اختبر موقع المؤشر الأولي مقابل العناصر بترتيب عكسي لترتيب الرسم، واختر أول عنصر يطابقه.
- احتفظ بمعرّف العنصر المحدد ومركزه الأصلي طوال الإيماءة.
- احسب كل مركز مقترح انطلاقًا من ذلك المركز الأصلي مضافًا إليه إجمالي إزاحة الإيماءة.
- طبّق حدود مساحة العمل وقواعد التصادم للحصول على مركز مقبول.
- حدّث النموذج، ودع Canvas يعيد الرسم انطلاقًا منه.
- ألغِ التحديد عند انتهاء الإيماءة، وحدد آلية التنظيف عند انقطاع التفاعلات.
الفرق بين الحركة الإجمالية والتزايدية مهم. يعرّف SwiftUI القيمة translation بأنها الحركة منذ بداية الإيماءة. وإضافة هذه القيمة الإجمالية مرارًا إلى مركز جرى تحديثه بالفعل تؤدي إلى تراكم حركة أكبر من المطلوب. استخدم المركز الابتدائي المحفوظ في كل مرة. يتيح SwiftUI القيم startLocation وlocation وtranslation عبر قيمة الإيماءة، كما يتيح تحديد مساحة إحداثيات الإيماءة. DragGesture.Value، DragGesture
حافظ على ثبات التحديد أثناء السحب. قد تؤدي إعادة عملية تحديد العنصر الأولية عند كل تحديث للمؤشر إلى تبديل العنصر المحدد عندما يعبر المؤشر شكلًا آخر. وبالمثل، فإن الحفاظ على الإزاحة الأولية بين المؤشر ومركز الشكل يجنب قفز الشكل بحيث يصبح مركزه فجأة تحت المؤشر. هذه قواعد تصميم للتطبيق، وليست سلوكيات يوفرها Canvas تلقائيًا.
مثال عملي: اكتشاف التداخل مقابل إيقاف الحركة
لنفترض وجود دائرتين توضيحيتين، نصف قطر كل منهما 20 نقطة. تبدأ الدائرة A عند (100, 100)، وتقع الدائرة B الثابتة عند (200, 100). يطلب المستخدم أن يكون مركز A عند (190, 100). ستكون المسافة بين مركزيهما 10 نقاط، بينما مجموع نصفي القطر 40. إذن، يتداخل الموضع المقترح.
إذا أردت إظهار مؤشر للتداخل، فاقبل الموضع المقترح وغيّر الرسم. أما في سحب أفقي متوقف عند العائق، فأول موضع تلامس هو (160, 100): إذ تفصل بين المركزين عندئذٍ 40 نقطة. يفترض هذا الحساب بقاء B ثابتة، واحتفاظ الدائرتين بحجميهما، واتّباع A لمسار أفقي.
لنفترض الآن أن تحديثًا واحدًا للمؤشر يطلب (260, 100). لن يكشف اختبار هذه النقطة النهائية وحدها عن أي تداخل، رغم أن مسار الحركة يمر عبر B. يحتاج تنفيذ السحب المتوقف عند العائق إلى اختبار تقاطع على امتداد المسار، أو إلى طريقة صريحة أخرى للتحقق من المسار بين النقطتين. إن رفض النقاط النهائية المتداخلة وحده لا يحقق سلوك «التوقف عند أول عائق».
هذا المثال هو السبب الرئيسي لضرورة تحديد استجابة التصادم بدقة. قد تكفي بعض الاختبارات الهندسية للنقاط النهائية لتنفيذ ميزة تمييز التداخل. لكن دعم الانزلاق حول العوائق، أو التلامسات المتزامنة، أو تحريك العناصر المجاورة يوسّع نطاق خوارزمية الحل المخصصة بدرجة كبيرة. يظل Canvas خيارًا للرسم، لكن التطبيق يتحمل مسؤولية هذا السلوك الإضافي.
متى يصبح SpriteKit الخيار الأنسب؟
يصبح SpriteKit جذابًا عندما يُفترض أن تنشأ استجابة التصادم عن محاكاة. إن إرفاق SKPhysicsBody بعقدة يتيح إجراء حسابات تشمل الجاذبية والاحتكاك والتصادمات؛ ثم تحدّث المحاكاة مواضع العقد واتجاهاتها. وهو يدعم الأجسام ذات الحجم للأجسام، والأجسام الحافية للحدود. SKPhysicsBody
في نموذج أولي أول، استخدم دائرتين متباعدتين وحدًا مستطيلًا لمساحة العمل. امنح الدائرتين جسمين دائريين متماثلين، وعطّل الجاذبية لمساحة عمل شبيهة بسطح طاولة، وحدد ما إذا كان للدوران أي فائدة في التفاعل. اضبط الاحتكاك والارتداد والتخميد عمدًا بدلًا من الاعتماد على القيم الافتراضية. تحدد هذه الخصائص جوانب مختلفة من السلوك الفيزيائي. SKPhysicsBody
يمكن لـ SwiftUI استضافة المشهد مباشرةً باستخدام SpriteView، التي توثقها Apple على أنها عرض SwiftUI يرسم مشهد SpriteKit. يبدأ توفرها الموثق على Mac من macOS 11، بينما يبدأ توفر Canvas من macOS 12. لذلك تدخل إصدارات النشر المستهدفة ضمن القرار، مع أن سلوك التفاعل يظل المعيار الأساسي. SpriteView، Canvas
اضبط استجابة التصادم بمعزل عن الإشعارات
عيّن فئات إلى الأشكال والحدود، ثم اضبط قناعي التفاعل صراحةً:
- تحدد
categoryBitMaskالفئات التي ينتمي إليها الجسم. - تختار
collisionBitMaskالفئات التي يمكنها التأثير فيزيائيًا في ذلك الجسم. - تطلب
contactTestBitMaskإشعارات التلامس للفئات المحددة.
يُقيّم قناع التصادم على نحو مستقل لكل جسم. وللحصول على استجابة متبادلة اعتيادية بين شكلين ديناميكيين، اضبط كلا الجسمين ليستجيبا لفئة الجسم الآخر. أما الإبلاغ عن التلامس فله قاعدة مختلفة: يمكن لمقارنة مطابقة بين الفئة وقناع التلامس في أي من الاتجاهين أن تولّد إشعارًا. إن تفعيل الإشعارات وحده لا يضبط الحجب الفيزيائي. collisionBitMask، contactTestBitMask
إذا استجابت الأجسام فيزيائيًا لكن لم يصل أي استدعاء، فتحقق من قناع التلامس ومفوّض التلامس في عالم الفيزياء. وإذا وصلت الاستدعاءات بينما تمر الأشكال بعضها عبر بعض، فتحقق من أقنعة التصادم ووحدة التحكم بالحركة. تفصل هذه الفحوص بين إعداد الإشعارات ومشكلة الحركة الفعلية.
من المسؤول عن الموضع أثناء سحب المستخدم؟
يتطلب السحب في SpriteKit وحدة تحكم صريحة. محرك الفيزياء بحد ذاته لا يتبع المؤشر. اختر بين الوضع المباشر والحركة التي تقودها المحاكاة وفق السلوك الذي حددته سابقًا.
في الوضع المباشر، يعيّن كود التطبيق موضع العقدة المسحوبة. يناسب هذا الأسلوب الحالات التي يجب أن يضع فيها المؤشر العنصر بدقة، لكنه لا يضمن وحده مسارًا محسوبًا فيزيائيًا بين المواضع المتتالية. إن تعطيل isDynamic يجعل الجسم يتجاهل القوى والنبضات؛ لكنه لا ينشئ خوارزمية لحل السحب. إذا غيّرت هذه الخاصية مؤقتًا، فحدد متى تُعاد إلى قيمتها السابقة، وما إذا كان ينبغي للإفلات الإبقاء على الحركة أو إزالتها. isDynamic
في السحب الذي تقوده المحاكاة، احتفظ بموقع المؤشر بوصفه هدفًا، واستخدم القوى أو وحدة تحكم قائمة على نابض لتوجيه جسم ديناميكي نحوه. حينها تستطيع المحاكاة حل التلامسات أثناء تحريك الجسم. والمقابل هو حدوث تأخر مقصود أو ابتعاد عن المؤشر عندما يعوق جسم آخر الحركة. يدعم SpriteKit تطبيق القوى، كما تصل SKPhysicsJointSpring بين جسمين فيزيائيين بسلوك نابضي. وهذه لبنات بناء تتطلب ضبطًا خاصًا بالتطبيق. SKPhysicsBody، SKPhysicsJointSpring
تجنب تعيين مواضع المؤشر باستمرار مع توقع أن يتحكم الفيزياء، في الوقت نفسه وبشكل مستقل، في الموضع ذاته. ينفذ SpriteKit تسلسل الإطارات الموثق عبر مراحل منفصلة تشمل تحديثات التطبيق والإجراءات والفيزياء والاستدعاءات اللاحقة. ومن قواعد الملكية المفيدة أن يحدّث الإدخال هدف السحب، وأن يطبّق منطق المشهد وحدة التحكم، وأن تنتج الفيزياء موضع الجسم الناتج. Responding to Frame-Cycle Events
للاستدعاءات المتعلقة بالتلامس حدود موثقة أيضًا. تقول Apple إنها تعمل أثناء خطوة محاكاة الفيزياء، حين لا يكون تعديل عالم الفيزياء مدعومًا، وتكون التغييرات التي تُجرى على الأجسام المحاكاة ذات سلوك غير محدد. سجّل العمل المعلّق في الاستدعاء، وطبّق تغييرات الفيزياء أثناء تحديث لاحق، باتباع نمط Apple القائم على العلم والتحديث. SKPhysicsContactDelegate
كيف ينبغي أن ينتقل إدخال macOS بين الأطر؟
اختر جهة واحدة مسؤولة عن الإدخال في مساحة العمل. يمكن لتنفيذ Canvas إرفاق DragGesture من SwiftUI بسطح التفاعل. ويمكن لسطح AppKit مخصص معالجة أحداث المستجيب؛ إذ يستقبل mouseDragged(with:) الحركة عندما يكون زر الفأرة الأيسر مضغوطًا. تجنب جعل إيماءة SwiftUI متراكبة ومعالج AppKit يحرّكان العنصر المحدد نفسه كلٌّ على حدة. DragGesture، mouseDragged(with:)
لإنشاء جسر من AppKit إلى SpriteKit، حوّل الإحداثيات عند كل حد فاصل. يُعبّر عادةً عن NSEvent.locationInWindow ضمن نظام الإحداثيات الأساسي للنافذة المرتبطة. حوّل تلك النقطة إلى إحداثيات العرض المستضيف، ثم حوّلها من إحداثيات العرض إلى إحداثيات المشهد. توفر Apple convertPoint(fromView:) للخطوة الأخيرة، وتشترط تقديم المشهد قبل استدعائها. يُعبّر عن موضع العقدة ضمن نظام إحداثيات العنصر الأب، لذا قد تتطلب العقد المتداخلة تحويلًا إضافيًا. locationInWindow، convertPoint(fromView:)، position
استخدم SpriteView لاستضافة مباشرة ضمن SwiftUI. وإذا احتجت إلى عرض AppKit مخصص، فإن NSViewRepresentable توفر الإنشاء والتحديث والتنسيق وإنهاء الاستخدام. أبقِ مسؤولية تخطيط العرض المُضمّن على عاتق SwiftUI: تحذر Apple صراحةً من تعيين الإطار أو الحدود التي تديرها بنفسك. NSViewRepresentable
احتفظ بالمشهد لدى جهة مالكة مستقرة بدلًا من إنشاء مشهد جديد كلما أعاد SwiftUI حساب محتوى العرض. واحتفظ بنموذج العناصر بالطريقة نفسها في Canvas. تشرح وثائق نماذج SwiftUI كيف تحافظ الحالة المُدارة على نسخة عبر إعادة إنشاء العرض؛ ويتطلب نهج Observation إصدار macOS 14 أو أحدث. تعامل مع أوامر المشهد ولقطات النموذج على أنها عمليات نقل مقصودة، حتى لا تتنافس نسختان قابلتان للتغيير على تحديد المواضع. Managing model data in your app
ما الذي ينبغي اختباره قبل الالتزام بأي من النهجين؟
استخدم مساحة عمل اصطناعية صغيرة لاختبار التفاعلات الصعبة قبل إضافة اللمسات المرئية:
- أمسك قرب حافة شكل وتأكد من الحفاظ على الإزاحة الأولية.
- اسحب ببطء إلى أن تصطدم بعنصر آخر، ثم كرر ذلك بحركة واحدة كبيرة للمؤشر تعبر العنصر.
- اسحب باتجاه زاوية تضم شكلين متجاورين.
- أفلت أثناء ملامسة عنصر آخر، وتحقق من سلوك الاستقرار المقصود.
- غيّر حجم مساحة العمل وتحقق من تحديد العناصر والحدود وتحويل الإحداثيات.
- أعد إنشاء محتوى SwiftUI المحيط وتأكد من استمرار مواضع العناصر.
- قاطع عملية سحب وتأكد من تنظيف التحديد وأي إعدادات فيزيائية مؤقتة.
في SpriteKit، فعّل SKView.showsPhysics أثناء التحقق من تطابق هندسة الجسم مع منطقة التصادم المقصودة. وبالنسبة إلى الأجسام الصغيرة والسريعة الحركة، قيّم usesPreciseCollisionDetection؛ توثق Apple تكلفته الحسابية الأعلى ودوره في اكتشاف التصادمات التي قد تفوتها الفحوص الاعتيادية القائمة على الإطارات. ولا ينبغي أن يحل محل وحدة تحكم متماسكة للسحب. SKView، usesPreciseCollisionDetection
اختر Canvas عندما تظل قواعد الوضع واضحة ومحدودة ويسهل التعبير عنها مباشرةً. واختر SpriteKit عندما يكون التفاعل الفيزيائي بين الأشكال جزءًا من المهمة. في كلا التنفيذين، يبدأ السحب الموثوق على سطح المكتب بعنصر محدد واحد، وتحويلات صريحة للإحداثيات، ومكوّن واحد مسؤول عن كل تحديث للموضع.
المصادر
- Canvas — Apple
- DragGesture — Apple
- DragGesture.Value — Apple
- Choosing Between Structures and Classes — Apple
- About Collisions and Contacts — Apple
- SKPhysicsBody — Apple
- SpriteView — Apple
- collisionBitMask — Apple
- contactTestBitMask — Apple
- isDynamic — Apple
- SKPhysicsJointSpring — Apple
- Responding to Frame-Cycle Events — Apple
- SKPhysicsContactDelegate — Apple
- mouseDragged(with:) — Apple
- locationInWindow — Apple
- convertPoint(fromView:) — Apple
- position — Apple
- NSViewRepresentable — Apple
- Managing model data in your app — Apple
- SKView — Apple
- usesPreciseCollisionDetection — Apple