كيف أطلقنا 2GIS تحت CarPlay وما زلنا نتفكك


تحية! اسمي فانيا ، أنا أكتب تطبيق جوال 2GIS لنظام iOS. اليوم ستكون هناك قصة حول كيفية ظهور متصفحنا في CarPlay. سوف أخبركم كيف قمنا ، من خلال هذه الوثائق والأدوات غير المكتملة ، بإنشاء منتج عامل ووضعه في AppStore.


بضع كلمات عن CarPlay



أولاً ، القليل من المواد لفهم بعض جوانب CarPlay والأسباب التي تجعلنا نتخذ قرارات معينة.


CarPlay ليس نظام تشغيل داخل نظام تشغيل آخر ، حيث أن العديد من المقالات تكتب عنه. إذا كان الأمر تقريبًا ، فإن CarPlay هو بروتوكول للعمل مع شاشة خارجية لشاشة وحدة الرأس ؛ صوت من مكبرات صوت السيارة الشاشات التي تعمل باللمس ، لوحات اللمس ، غسالات وأجهزة الإدخال الأخرى.


بمعنى أن الكود القابل للتنفيذ بالكامل يقع مباشرة في التطبيق الرئيسي (وليس حتى في ملحق منفصل!) هذا رائع جدًا: للحصول على ميزات جديدة ، لا تحتاج إلى تحديث الراديو أو حتى الجهاز ، فأنت بحاجة فقط إلى تحديث iOS.


في WWDC 2018 Keynote ، تم تزويدنا بفرصة لإنشاء تطبيقات الملاحة لـ CarPlay ، مما جعلنا سعداء للغاية. بعد العرض التقديمي مباشرة ، أرسلنا طلبًا للحصول على إذن بالتطوير لـ CarPlay. في الطلب ، كان من الضروري إظهار أن تطبيقنا قادر على التنقل.


بينما كنا ننتظر إجابة من Apple ، كانت هناك محاضرة تحدثت فيها ، باستخدام نموذج تطبيق CountryRoads ، عن العمل مع CarPlay.framework. لم تتحدث المحاضرة عن المزالق والخفايا عند العمل مع CarPlay ، لكنها ذكرت أنه بعد الاتصال براديو CarPlay ، سيعمل التطبيق في وضع الخلفية.


العصا الأولى في العجلات


التطبيق في الخلفية بخيبة أمل لنا. كان هناك سببان لهذا:


  1. نحن لا نعمل في الخلفية. مرة واحدة ترك هذا القيد لأسباب فنية والحفاظ على الطاقة.
  2. خريطتنا مكتوبة بلغة OpenGL (نعم ، تم إهمالها ، نعم ، ليست معدنية ، كلنا نعرف ذلك) ، ولا يعمل برنامج OpenGL في حالة الخلفية. في أحسن الأحوال ، تحصل على منظر أسود ، وفي أسوأ الأحوال ، تعطل.

كان لا يزال من الممكن التعامل مع العمل في الخلفية ، ولكن البطاقة تحتاج بالتأكيد إلى حل. ثم جاءت الفكرة لجعلها من خلال MKMapView القياسية. إلى أن تبدأ في إلقاء الحجارة علينا لفكرة استخدام بطاقات Apple القياسية ، فسأشرح لك: سنستخدم MKMapView ، ولكن ليس بطاقات Apple.


والحقيقة هي أن MKMapView يمكن تحميل البلاط طرف ثالث. البلاط عبارة عن حاويات مستطيلة خاصة للأنسجة. لقد تحولنا للتو إلى servochka الذي يعرف كيفية إعطاء البلاط. هناك كود التنفيذ على جيثب.


جواب أبل


لقد تلقينا إجابة من Apple ، والتي ، بالإضافة إلى إذن بالتطوير ، تلقينا أيضًا وثائق "للنخبة" ، رمز تطبيق نموذج CountryRoads (تم عرضه في محاضرة WWDC) ، والأهم من ذلك ، com.apple.developer.carplay-maps . تتم كتابة هذا المفتاح في ملف الاستحقاقات بالقيمة YES ، بحيث يفهم النظام أنه يمكنك معالجة الأحداث من CarPlay عند بدء تشغيل التطبيق الخاص بك.


دون انتظار سباق مع القصص المختارة للتنمية ، صعدت لتنزيل Xcode Beta. كانت المحاولة الأولى لجمع 2GIS فاشلة. ولكن مشروع تطبيق نموذج CoutryRoads كان قادراً على تجميعها لمحاكاة.


قبل كل فتح لإطار محاكاة CarPlay ، كان يجب تخصيص الأخير من خلال نافذة كهذه:



للقيام بذلك ، كان عليك كتابة سطر في المحطة الطرفية: defaults write com.apple.iphonesimulator CarPlayExtraOptions -bool YES


لسبب ما ، لم ينجح هذا - اضطررت إلى تشغيله على أصغر جهاز محاكاة تقريبًا بدقة 800 × 480 نقطة ومقياس × 2. في الوقت الحالي ، يعمل هذا الإعداد ويساعد كثيرًا.


بعد إنشاء مشروع نموذجي ومزود بالوثائق ، بدأت أفهم ما كان يحدث.
أول شيء أدركته: تتكون تطبيقات التنقل لـ CarPlay من طبقات القاعدة وطريقة العرض.



عرض قاعدة هي خريطتك. في هذه الطبقة ، يجب أن يكون هناك خريطة فقط ، ولا توجد طرق عرض وعناصر تحكم أخرى.


النماذج عبارة عن مجموعة إلزامية غير قابلة للتخصيص تقريبًا من عناصر واجهة المستخدم لعرض المسارات والمناورات وجميع أنواع القوائم وما إلى ذلك.


تطوير بيتا


دعنا ننتقل إلى كتابة التعليمات البرمجية. أول شيء يجب القيام به هو تطبيق عدة طرق CPApplicationDelegate المطلوبة في ملف ApplicationDelegate.


 func application( _ application: UIApplication, didConnectCarInterfaceController controller: CPInterfaceController, to window: CPWindow ) {} func application( _ application: UIApplication, didDisconnectCarInterfaceController controller: CPInterfaceController, from window: CPWindow ) {} 

لنلقِ نظرة على التوقيع:


مع تطبيق UIApplication ، كل شيء واضح.
CPWindow هي الخلف لـ UIWindow ، وهي نافذة للعرض الخارجي لوحدة الرأس في الراديو.
CPInterfaceController - شيء يشبه نظير UINavigationController ، فقط من CarPlay.framework.


الآن ننتقل مباشرة إلى تنفيذ الأسلوب.


 func application( _ application: UIApplication, didConnectCarInterfaceController controller: CPInterfaceController, to window: CPWindow ) { let carMapViewController = CarMapViewController( interfaceController: controller ) let navigationController = UINavigationController( rootViewController: carMapViewController ) window.rootViewController = navigationController } 

في didConnect ، تحتاج إلى كتابة رمز مشابه للرمز الذي اعتدنا رؤيته في didFinishLaunching. CarMapViewController عبارة عن طريقة عرض أساسية (وحدة التحكم في الواقع ، ولكن لا بأس بها) ، وفقًا للوثائق.


إليكم الصورة التي حصلت عليها أخيرًا:



في مكان ما في هذا الوقت ، بدا لي أنه تم تمكين نظام إنشاء Xcode الجديد بشكل افتراضي ، وعلى الأرجح ، بسبب هذا ، لن يتم تطبيق 2GIS.


لقد فتحت Xcode ، أو قمت بتثبيت الإرث القديم (أو المستقر إلى حد ما ، دعنا نسمي نظام بناء بأسمائها بأسمائها الحقيقية) ، وتم تأكيد نظريتي: تم تجميع نظام 2GIS.


بعد أن قمت بتعيين نفس مفتاح القدرة ، أطلقت 2GIS تحت CarPlay ولم أر أي سجلات حول تبديل التطبيق إلى وضع الخلفية. أصبح الأمر غير مفهوم أكثر ، لأن مهندسي Apple من المشهد قالوا عن وضع الخلفية ، لكن من ناحية أخرى ، وعدونا بعرض محتوى من UIAlertView ، ونتيجة لذلك ، تم إهمال UIAlertView.


بعد أن قررت أنه ينبغي أن يكون الأمر كذلك ، لم أكن عناء مع MKMapView. هذا سيحرمنا في وضع عدم الاتصال ويجعلنا نعيد كتابة الطرق.


مشكلة بطاقة واحدة


لم يكن لدي الوقت لنفرح من الأخبار التي تفيد بأن CarPlay ستحصل على خريطتنا ، حيث واجهتني المشكلة التالية: نظرًا للميزات التقنية ، يمكن أن توجد خريطة واحدة فقط.
كان الحل السريع لهذه المشكلة ، وإن لم يكن أنيقًا للغاية.


عادةً ، عند استخدام 2GIS على CarPlay ، يكون الهاتف مقفلاً ويوجد في مكان ما على الرف. وبالتالي فإن الخريطة في هذه اللحظة على الهاتف ليست ضرورية بالفعل (لن تؤذي للبحث ، بالطبع). لذلك ، عندما وصلنا الهاتف بـ CarPlay ، قررنا استلام البطاقة من التطبيق الرئيسي وعرضها على شاشة CarPlay في الراديو. وعندما يتم قطع الاتصال ، على التوالي ، ارجع إلى التطبيق على الهاتف.


نعم ، إنه حل لنفسه ، لكنه سريع ، لكنه لا يزال يعمل ولم يكن مضطرًا لركل بضعة أوامر أخرى لتثبيت MVP.


الضوابط على الخريطة


لذلك ، حصلنا على خريطتنا على شاشة الراديو. من الضروري الآن القيام بالأشياء الأولى والواضحة لأي خريطة: عناصر التحكم للتكبير / التصغير والموقع الحالي وحركة الخريطة.



لنبدأ بالتكبير والموقع الحالي ، لأن عناصر التحكم هذه موجودة على الخريطة نفسها وليست هذه UIControl عادية. كما كتبت أعلاه ، فقط الخريطة هي في عرض القاعدة.


من أجل وضع عناصر التحكم هذه على البطاقة ، اضطررت للذهاب إلى الوثائق وتطبيق نموذج مرة أخرى. هناك قرأت عن القالب الأول - CPMapTemplate.



CPMapTemplate - قالب شفاف لعرض بعض عناصر التحكم على الخريطة والتماثلية من شريط التنقل. يتم إنشاؤه وإعداد مثل هذا:


 let mapTemplate = CPMapTemplate() self.interfaceController.setRootTemplate(mapTemplate, animated: false) 

بعد ذلك ، تحتاج إلى إنشاء عناصر التحكم هذه ووضعها على البطاقة.


 let zoomInButton = CPMapButton(…) let zoomOutButton = CPMapButton(…) let myLocationButton = CPMapButton(…) self.mapTemplate.mapButtons = [ zoomInButton, zoomOutButton, myLocationButton ] 

لكن تبين أن مجموعة mapButtons مضحكة ، لأنه بغض النظر عن عدد العناصر التي تضعها فيها ، سيستغرق الأمر العناصر الثلاثة الأولى فقط وعرضها على الشاشة. لن تتلقى أي أخطاء في السجل أو التأكيدات.


بعد ذلك ، تعرفت على كيفية تحريك الخريطة ، ووجدت ذلك في الوثائق:


 Navigation apps are designed to work with a variety of car input devices, and CarPlay does not support direct user interaction in the base view (apps do not directly receive tap or drag events). 

غريب ، وأعتقد ، وحصلت على مشاهدة كيف يتم ذلك في تطبيق نموذج CountryRoads. الجواب من خلال هذه الواجهة:



ليست مريحة للغاية ، ولكن بطريقة مختلفة ، فإن الوثائق لا تكذب ، أليس كذلك؟


نظرًا لأن مكان عناصر التحكم على الخريطة التي نفدها ، كان من الضروري إنشاء زر لوضع الخريطة في وضع "السحب" في هذا التماثل من navigationBar.


 let panButton = CPBarButton(…) self.mapTemplate.leadingNavigationBarButtons = [panButton] self.mapTemplate.trailingNavigationBarButtons = [] 

لكن الصفيفين الرائدين NavigationBarButton و trailingNavigationBarButton لم يخليا من نكتة أيضًا: بغض النظر عن عدد العناصر التي يشقونها ، فسيأخذون الأولين فقط. أيضا دون أخطاء في السجل والتأكيدات.


ولتفعيل وإلغاء تنشيط وضع السحب والإفلات ، يجب عليك كتابة:


 self.mapTemplate.showPanningInterface(animated: true) self.mapTemplate.dismissPanningInterface(animated: true) 

بناء وعرض الطرق على الخريطة


بعد ذلك ، بدأت في إعادة استخدام واجهة برمجة التطبيقات الحالية لإنشاء الطرق.


فقط للتجربة وفهم ماذا وكيف أفعل ، قررت أخذ نقطتين وبناء طريق بينهما. كانت النقطة A هي موقع المستخدم ، وكانت النقطة B هي مكتبنا الرئيسي في نوفوسيبيرسك.


قانون
 let choice0 = CPRouteChoice( summaryVariants: ["46 "], additionalInformationVariants: ["  "], selectionSummaryVariants: ["1  7 "] ) let choice1 = CPRouteChoice( summaryVariants: ["46 "], additionalInformationVariants: ["  "], selectionSummaryVariants: [“1  11 "] ) let startItem = MKMapItem(…) let endItem = MKMapItem(…) endItem.name = ",  ” let trip = CPTrip( origin: startItem, destination: endItem, routeChoices: [choice0, choice1] ) let tripPreviewTextConfiguration = CPTripPreviewTextConfiguration( startButtonTitle: " ”, additionalRoutesButtonTitle: “”, overviewButtonTitle: "" ) self.mapTemplate.showTripPreviews( [trip], textConfiguration: tripPreviewTextConfiguration ) 

على الشاشة حصلنا على عنصر تحكم مع وصف الطريق:



وضع التنقل


الطرق جيدة ، لكن الميزة الرئيسية للمتصفح هي التنقل. لكي تظهر ، يجب عليك كتابة ما يلي:


 func mapTemplate( _ mapTemplate: CPMapTemplate, startedTrip trip: CPTrip, using routeChoice: CPRouteChoice ) { self.navigationSession = self.mapTemplate.startNavigationSession(for: trip) } 

CPNavigationSession - فئة يمكنك من خلالها عرض بعض عناصر واجهة المستخدم الضرورية فقط في وضع التنقل.


لعرض المناورة ، يجب عليك:


 let maneuver = CPManeuver() maneuver.symbolSet = CPImageSet( lightContentImage: icon, darkContentImage: darkIcon ) maneuver.instructionVariants = [". "] maneuver.initialTravelEstimates = CPTravelEstimates(…) self.navigationSession?.upcomingManeuvers = [maneuver] 

ثم على شاشة الراديو نحصل على هذا:



لتحديث اللقطات للمناورة ، يجب عليك:


 let estimates = CPTravelEstimates(…) self.navigationSession?.updateEstimates(estimates, for: maneuver) 

إنه يعمل فقط!


عندما تكون الوظيفة الأساسية للمتصفح جاهزة ، قررت عرض هذه الطائرة في عرض تقديمي داخلي. لقد كان العرض التقديمي ناجحًا: فالجميع لديهم فكرة إكمال الملاح واختباره وإطلاقه في أسرع وقت ممكن.


بادئ ذي بدء ، طلبنا وحدة رأس حقيقية بدعم CarPlay. ثم ، كما يقولون ، بدأت الحرارة.


رائد AVH-Z500BT


توفير الملامح


بسبب إضافة مفتاح إمكانية جديد ، يلزم إعادة إنشاء ملفات التعريف. في التطور الطبيعي ، لا نفكر في الأمر ، لأن Xcode سيفعل كل شيء بمفرده. ولكن ليس في حالة المفتاح الخاص.


 Code Signing Error: Automatic signing is unable to resolve an issue with the "v4ios" target's entitlements. Automatic signing can't add the com.apple.developer.carplay-maps entitlement to your provisioning profile. Switch to manual signing and resolve the issue by downloading a matching provisioning profile from the developer website. 

لقد تسبب أيضًا في كسر CI ، نظرًا للتوزيع المحلي لإصدارات التطبيق ، فإننا نستخدم حساب مؤسسة ، لم نطلب فيه إذنًا لتطوير تطبيق CarPlay. لكن هذه قصة مختلفة تماما.


التصحيح


يمكنك الاتصال بـ CarPlay عبر Bluetooth أو Lightning. الممارسة تبين أن الطريقة الثانية هي أكثر شعبية. لم يكن راديونا الذي يعمل بتقنية Bluetooth يعرف كيف ، لذلك أثناء التطوير ، كان علي استخدام تصحيح Wi-Fi. إذا جربته في مشاريع أكثر صعوبة من عالم hello ، فأنت تعرف ما هو الجحيم.


بالنسبة لأولئك الذين لم يحاكموا ، أقول:

جمعت التطبيق عبر الهاتف بالسلك ، وعندها فقط ، قمت بتوصيل الهاتف بـ CarPlay ، عبر Wi-Fi ، وقمت بتحميله على الهاتف وتشغيله لعدة دقائق.
استغرق نسخ التطبيق على الهاتف حوالي 3 دقائق ، وبدأ تشغيل التطبيق لمدة دقيقة ، وبعد ذلك فقط بعد بدء التوقف عند نقاط التوقف بعد 15 ثانية فقط.


ثم أصبح الأمر مثيراً للاهتمام بالنسبة لي لماذا لم تصنع Apple أي DevKit (بحيث تعمل Apple-way ، إنها تعمل فقط وهذا كل شيء). لم تكن مريحة للغاية لتجميع موقف اختبار دون ذلك. حتى الآن ، مرة كل أسبوعين ، يسقط شيء ما - عليك أن تتذكر من الصور ما يجب التمسك به. من الجيد أن المسؤول ، عند تجميع هذا الموقف ، أخبر ماذا ولماذا.


أفضل إطار صنعناه على الإطلاق


في النهاية ، عندما تم تجميع كل شيء على جهاز حقيقي ، أصبح من الواضح أن ميزة "2GIS for CarPlay" ستكون بالتأكيد. لقد حان الوقت لفعل الجمال.


قضايا Viewport


كان من الضروري تكوين منفذ عرض الخريطة لرسم الطرق في المنطقة دون ضوابط غير ضرورية ، وليس فقط في الوسط. باختصار ، لجعلها تبدو مختلفة:



و هكذا:



كنت آمل في الحصول على نوع من layoutGuide مع المنطقة المرئية الحالية. بحيث يأخذ في الاعتبار شريط التنقل ، وعرض الطريق ، وعناصر التحكم على الخريطة. في الواقع ، لم أحصل على أي شيء. لا يزال من غير الواضح كيفية تكوين منفذ العرض ، لذلك لدينا رمز ثابت مثل:


 let routeControlsWidth = self.view.frame.width * 0.48 let zoomControlWidth = self.view.frame.width * 0.15 

بناء الممر ليس فقط بين نقطتين


في الإصدار الأول ، قررنا أن نأخذ أداة التصنيف الخاصة بنا من خلال CPGridTemplate:



المفضلة والمنزل / العمل من خلال CPListTemplate.



وبحث لوحة المفاتيح من خلال CPSearchTemplate:



لن أعرض الكود الخاص بالقوالب ، لأنه بسيط والوثائق المتعلقة به مكتوبة جيدًا (على الأقل حول شيء ما).


ومع ذلك ، تجدر الإشارة إلى المشكلات التي تم اكتشافها عند العمل معهم.

يمكن CPInterfaceController في التنقل مماثلة ل UIKit. ر. ه.


 self.interfaceController.pushTemplate(listTemplate, animated: true) self.interfaceController.presentTemplate(alertTemplate, animated: true) 

ولكن إذا حاولت تشغيل ، على سبيل المثال ، CPAlertTemplate ، فستتلقى تأكيدًا في السجلات بأنه لا يمكن تمثيل CPAlertTemplate إلا بشكل معتدلة.


ليس من الواضح لماذا لم تخفي Apple منطق الشرائح تحت الغطاء بدون إنشاء واجهة مثل:


 self.interfaceController.showTemplate(listTemplate, animated: true) 

كما أنه كسر القدرة على استخدام ورثة CPTemplate ، مثل وحدات التحكم في UIKit.


عند محاولة ، على سبيل المثال ، وضع وريثك في مكدس القالب ، تحصل على هذا:


 Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unsupported object <YourAwesomeGridTemplate: 0x60000060dce0> <identifier: 6CAC7E3B-FE70-43FC-A8B1-8FC39334A61D, userInfo: (null)> passed to pushTemplate:animated:. Allowed classes: {( CPListTemplate, CPGridTemplate, CPSearchTemplate, CPMapTemplate )}' 

اختبار والبق


تم اختباره بواسطة artemenko-aa . واحدة من الأخطاء الأولى التي وجدها ، ما زلنا لا نستطيع إصلاح.


والحقيقة هي أنه عندما تقوم بفصل الهاتف عن راديو CarPlay ، فإن الوكالة الدولية للطاقة تراقبنا بشكل متقطع - دون توضيح السبب. حتى فتح syslogs ، لا يوجد شيء واضح. لذلك إذا كان لديك فكرة عن كيفية إصلاح أو فهم السبب ، فلا تتردد في التعليق.


كان الخطأ التالي في نفس المكان ، ولكن بسلوك خاص. كتبت أعلاه أن طريقة didDisconnect لـ CPApplicationDelegate تسمى عندما يكون الهاتف مفصولاً عن CarPlay. وفي هذه الطريقة ، نعيد البطاقة من شاشة الراديو إلى التطبيق الرئيسي. تخيل عدد المشكلات التي سنواجهها إذا لم يتم استدعاء هذه الطريقة مرة واحدة على الأقل من أصل خمسة.


أصبح من الواضح أن هذه مشكلة في نظام التشغيل iOS ، وليس على وجه التحديد تطبيقنا ، لأن النظام برمته يعتقد أنه متصل بـ CarPlay.



لقد أبلغت عنه كرادار (مثل كل الأخطاء الأخرى). لقد طُلب مني إسقاط السجلات باستخدام ملف التعريف هذا ، لكنني لم أستطع الإجابة على الدعم لبعض الوقت ، لذا أغلقوا الرادار.


نظرًا لأن Apple لم تخطط لفعل أي شيء ، فيجب تجاوز المشكلة بمفردها ، حيث تم تكرارها كثيرًا.


ثم تذكرت أن حصة الأسد من الاتصالات بـ CarPlay تمر عبر Lightning. هذا يعني أن الهاتف يشحن وقت الاتصال ، وعند انقطاع الاتصال ، يتوقف الشحن. وإذا كان الأمر كذلك ، فيمكنك الاشتراك في حالة البطارية ومعرفة الوقت الذي توقف فيه الهاتف عن الشحن وانفصل عن CarPlay.


المخطط ضعيف ، لكن لم يكن لدينا خيار. لقد ذهبنا بهذه الطريقة ، وقد نجحت!



لحسن الحظ ، تمت إزالة هذا العكاز منذ فترة طويلة من التعليمات البرمجية: قام مطورو Apple بإصلاح كل شيء في أحد إصدارات iOS.


قصة اثنين من المحررين


كانت عملية إعادة التوجيه الأولى متعلقة بالبيانات الوصفية. قال نص الافتتاحية أن وصفنا (وليس ملاحظات الإصدار) لا يقول إننا ندعم CarPlay. كما يمكنك تخمين ذلك ، لم يكن هذا المبدأ التوجيهي للمراجعة ولا خرائط Google نفسها. لم نناقش (لأنه عادة ما يكون أطول من تحرير البيانات الوصفية) ، قمنا بنسخ السطر من Release Notes to Description وبدأنا في انتظار مراجعة جديدة.


حدث الافتراض الثاني بسبب قائمة المدن. 2GIS لديه ميزة رائعة جدا - وضع التشغيل الكامل دون اتصال. هذه الميزة أطلقت النار علينا في الساق.


عند توصيل تطبيق بدون مدينة قائمة بـ CarPlay ، فإننا لا نعرض الخريطة ، لأنه لا يوجد شيء للعرض. ولهذا كنا من المقرر. كان الحل بسيطًا: تنبيه بدون أزرار ، والذي يشير إلى أنك تحتاج إلى تنزيل المدينة.



ما لا يمكنك التحدث عنه


حركة خريطة لفتة


في الوقت نفسه تقريبًا ، خرج المستكشف الموجود أسفل CarPlay من خرائط Google - وهناك يمكنك تحريك الخريطة باستخدام الإيماءات الموجودة على الشاشة. واجهات برمجة التطبيقات الخاصة ، أعتقد ، هذا واضح! جاء شباب Google للتو من مبنى قريب وقالوا ما يحتاجون إليه. بعد كل شيء ، تقول الوثائق:


 Navigation apps are designed to work with a variety of car input devices, and CarPlay does not support direct user interaction in the base view (apps do not directly receive tap or drag events). 

ومع ذلك ، ما زلت قررت التأكد من أنني حصلت على googled ، على الرغم من أنه كان بلا جدوى تقريبًا ، لأنه لا توجد مقالات تقنية حول تطبيقات CarPlay Navigation. ومع ذلك ، تمكنت من العثور على شيء مفيد ، وبشكل مفاجئ ، على موقع Apple الإلكتروني .


في الإرشادات ، عثرت على مقطع فيديو يقول أن الوثائق تكذب بوقاحة. يُظهر الفيديو كيف يمكنك الاستمرار في سحب الخريطة بالإيماءات. أدركت أنني لم أفهم شيئًا ، وأن الشيء الوحيد المتبقي بالنسبة لي هو فتح CarPlay.framework ومراجعة جميع ملفات h.


وها! أجد في CPMapTemplate المفوض الخاص به CPMapTemplateDelegate ، حيث توجد 3 طرق تبدو وكأنها تصرخ أنك إذا قمت بتنفيذها ، فيمكنك التحكم في إيماءات الخريطة.


3 طرق

/ * يتم الاتصال به عندما تبدأ إيماء المقلاة. قد لا يتم استدعاؤه عند الاتصال ببعض أنظمة CarPlay.
/
خريطة func العامة الاختيارية TemplateDidBeginPanGesture (_ mapTemplate: CPMapTemplate)


/ * يتم الاتصال به عند تغيير إيماء المقلاة. قد لا يتم استدعاؤه عند الاتصال ببعض أنظمة CarPlay.
/
خريطة func العامة الاختيارية (_ mapTemplate: CPMapTemplate، didUpdatePanGestureWith translation translation: CGPoint، speed: CGPoint)


/ * يسمى عندما تنتهي لفتة عموم. قد لا يتم استدعاؤه عند الاتصال ببعض أنظمة CarPlay.
/
خريطة func العامة الاختيارية (_ mapTemplate: CPMapTemplate ، didEndPanGestureWithVelocity speed: CGPoint
)


قمت بتنفيذها وتشغيل التطبيق على جهاز محاكاة - لا شيء يعمل. لم يكن لدي وقت للاضطراب ، أدركت أن جهاز المحاكاة يمكن أن يكون بنفس جودة الوثائق ، ووضعه على الجهاز. كل شيء بدأ ، والسعادة لا تعرف حدودا!


حقيقة ممتعة: يحتاج راديو CarPlay إلى ربع الشاشة لفهم أن إيماءة عموم قد بدأت. أريد أن أشير إلى أن UIPanGestureRecognizer يحتاج إلى 10 نقاط فقط.


توحيد واجهة المستخدم على مسجلات شريط الراديو المختلفة


لقد تلقينا نداءًا مؤيدًا: المستخدم لديه sajest واحد فقط يزحف في البحث ، على الرغم من أنه قد يكون هناك المزيد. اعتقدت أنه أمر غريب ، لأنه يوجد على كل الشاشات خط واحد مناسب. طلبت لقطة للشاشة:



وهذا مختلف تمامًا عن واجهة مستخدم CPSearchTemplate التي عرضتها أعلاه. ويجب أن يؤخذ ذلك في الاعتبار أثناء التطوير ، على الرغم من أنه لا يزال من المستحيل فهم عدد الخلايا الموجودة في اللوحة أدناه والتي يمكن وضعها على الشاشة.


التحكم في الحد الأقصى للسرعة


نظرنا إلى الإحصاءات وأدركنا أنهم يستخدمون المستكشف لـ CarPlay ونحتاج إلى نقله على الأقل إلى مستوى المستكشف في التطبيق الرئيسي. بادئ ذي بدء ، قررنا إضافة التحكم في الحد الأقصى للسرعة. بالطبع ، كانت هناك بعض المشاكل.


السؤال رقم واحد: أين مكان؟


بالتفتيش حول ملفات .h في CPWindow مرة أخرى ، لقد وجدت layoutGuide فضوليًا:
var mapButtonSafeAreaLayoutGuide: UILayoutGuide


وهذا تبين أن ما نحتاجه. سيطرتنا تناسب تماما:




السؤال الثاني: هل هذا قانوني بشكل عام؟


والحقيقة هي أن السيطرة من الناحية الفنية على عرض القاعدة. ولا يمكن أن يحتوي العرض الأساسي وفقًا للوثائق على أي شيء باستثناء الخريطة:


 The base view is where the map is drawn. The base view must be used exclusively to draw a map, and may not be used to display other UI elements. Instead, navigation apps overlay UI elements such as the navigation bar and map buttons using the provided templates. 

لكن المراجعين أخطونا في AppStore ، مما يعني أنه لا يزال من الممكن بناء عناصر التحكم المتعلقة بالملاحة.


البحث الصوتي




بطريقة جيدة ، كان لا بد من القيام بهذه الميزة في المقام الأول ، لكننا جمعنا العديد من المهام من الدين الفني الذي حال دون تنفيذ البحث الصوتي عن CarPlay. وكانت هذه المهمة ليست بهذه البساطة.


المشكلة الأولى: الرسوم المتحركة. الحقيقة هي أنه في CPVoiceControlTemplate لا توجد طريقة لعمل الرسوم المتحركة القياسية. كان يجب جمع الرسوم المتحركة للتعرف على الكلام والبحث كل على حدة من الصور والإشارة إلى مقدار الوقت الذي ذهبوا فيه.


 for i in 1...12 { if let image = UIImage(named: "carplay_searching_\(i)") { images.append(image) } } let image = UIImage.animatedImage(with: images, duration: 0.96) 

يبدو ، كما تعتقد ، ليس حقًا ، لكنني لا أريد تضخيم حجم التطبيق.


المشكلة الثانية: الوصول. تظهر التنبيهات الخاصة بالوصول إلى الميكروفون والتعرف على الكلام على شاشة الهاتف. اضطررت إلى الكتابة على شاشة الراديو التي يحتاجها المستخدم لالتقاط الهاتف ، ومنح إذن وعندها فقط استخدم المستكشف على الراديو. مريح جدا!


السيارات الأيمن بالسيارة.


تم إرسال لقطة شاشة تم فيها تحويل واجهة المستخدم الخاصة بالتطبيق بالكامل رأسًا على عقب!



وبالطبع ، ظل منفذ عرض الخريطة بالطريقة التي صممنا بها ، لأن لا أحد يتوقع أن يكون هناك إعداد منفصل لسيارات المقود الأيمن. لم أجد كيفية التغلب على هذا "بشكل صحيح" ، لكنني لاحظت أنه نظرًا لأن التحكم في حد السرعة لدينا يكمن في مخطط التخطيط لعناصر التحكم في الخريطة ، فقد انتقل إلى الجانب الأيسر.


Ultrafix لم يمض وقت طويل في المقبلة. لقد فعلوا ذلك بوقاحة ، لكنه نجح.


 let isLeftWheelCar = self.speedControlViewController.view.frame.origin.x > self.view.frame.size.width / 2.0 

آمل حقًا أن يكون هناك حل صحيح ، ولم أقرأه.


هذا كل شيء بالنسبة لي. إذا كنت تخطط فجأة لإنشاء متصفح خاص بك ضمن CarPlay ، ضع في اعتبارك أن الوثائق والإطار غير كاملين. المنصة جديدة تمامًا ، ولا أحد يعرف شيئًا ، وأبل ليست في عجلة من أمرها لتبادل المعرفة.

Source: https://habr.com/ru/post/ar452638/


All Articles