سفارشی‌سازی چیدمان پیمایش

نمونه پویانمایی که چیدمان پیمایش سفارشی‌شده را در iOS نشان می‌دهد

توجه: این سند از قراردادهای نام‌گذاری Swift برای کلاس‌ها، پروتکل‌ها، و میاناهای برنامه‌سازی کاربردی استفاده می‌کند.

از سفارشی‌سازی چیدمان برای کنترل سلسله‌مراتب نما و جای‌گذاری صفحه برای نماهای ارائه‌شده توسط Google و نماهای سفارشی کسب‌وکار خودتان استفاده کنید.

به‌جای افزودن نماهای سفارشی به جایگاه‌های چیدمان ازپیش تعریف‌شده، یک نماینده چیدمان را برای مدیریت چیدمان کل صفحه‌نمایش پیاده‌سازی کنید. درطول انتقال‌های حالت ناوبری، نماینده چیدمان شما مؤلفه‌های ارائه‌شده توسط Google را دریافت می‌کند، مانند کارت پیچ، کارت زمان تقریبی رسیدن، و دکمه‌ها. این عناصر را بااستفاده از محدودیت‌های استاندارد «چیدمان خودکار» iOS در کنار محتوای سفارشی خودتان قرار دهید.

از این چارچوب برای قرار دادن اطلاعات سفارشی کسب‌وکار (مثل وضعیت سفارش یا دستورالعمل‌های تحویل گرفتن) دقیقاً در صفحه استفاده کنید و درعین‌حال از هم‌پوشانی نماهایتان جلوگیری کنید.

نحوه عملکرد سفارشی‌سازی چیدمان

سفارشی‌سازی چیدمان از الگوی طراحی واگذاری استفاده می‌کند. به‌جای اینکه مؤلفه‌های میانای کاربر به‌طور خودکار روی صفحه‌نمایش کشیده یا جای‌گذاری شوند، کیت توسعه نرم‌افزار آن‌ها را مستقیماً به کلاس سفارشی‌ای که شما می‌نویسید (نماینده چیدمان) منتقل می‌کند.

برای سفارشی‌سازی کردن چیدمان، کلاسی ایجاد کنید که با پروتکل NavigationLayoutDelegate مطابقت داشته باشد و نمونه‌ای را به روش setLayoutDelegate(_:) از GMSMapView خود ارسال کنید. هرزمان که وضعیت ناوبری تغییر می‌کند (مثلاً از نقشه مبنا به راهنمایی گام‌به‌گام فعال می‌رود)، کیت توسعه نرم‌افزار روش‌های فراخوانی را در نماینده شما اجرا می‌کند و مؤلفه‌های واسط کاربر موجود برای آن وضعیت خاص را ارائه می‌دهد.

برای ارائه تجربه کاربری یکپارچه و بی‌وقفه، این چارچوب جداسازی واضحی از مسئولیت بین برنامه شما و کیت توسعه نرم‌افزار ایجاد می‌کند. از نماینده چیدمان برای سفارشی‌سازی موارد زیر استفاده کنید:

  • ساخت سلسله‌مراتب نما: دقیقاً انتخاب کنید کدام مؤلفه‌های Google و نماهای کسب‌وکار سفارشی برای هر وضعیت واسط کاربر به صفحه اضافه شود.
  • موقعیت هر عنصر: لنگرها، حاشیه‌ها، و جای‌گذاری چیدمان دقیق صفحه‌نمایش را تنظیم کنید. از اعمال محدودیت‌های عرض یا ارتفاع سفارشی برای مؤلفه‌های Google خودداری کنید، زیرا مؤلفه‌های Google ابعاد داخلی خود را محاسبه می‌کنند.
  • قاب‌بندی نقشه مبنا: مرزهای قابل‌مشاهده دوربین نقشه را بااستفاده از مختصات مؤلفه ناحیه نمایش تعریف کنید.
  • لایه‌بندی صفحه: تصمیم بگیرید که نماهای سفارشی در بالا، پایین، یا درکنار کنترل‌های داخلی Google شناور باشند.

درعین‌حال، نمی‌توانید مؤلفه‌های Google زیر را با نماینده چیدمان سفارشی‌سازی کنید:

  • ابعاد مؤلفه: اندازه و ابعاد داخلی مؤلفه‌های ارائه‌شده توسط Google که کیت توسعه نرم‌افزار به‌طور خودکار محاسبه می‌کند.
  • شرایط راه‌انداز: وقتی پیام‌واره‌ها یا هشدارهای پویا براساس داده‌های هم‌زمان مسیر ظاهر می‌شوند.

اصول پیاده‌سازی

هنگام نوشتن نماینده چیدمان، قوانین زیر را در ذهن داشته باشید تا از اشکالات چیدمان یا ازکارافتادن زمان اجرا جلوگیری کنید:

  • پیش‌فرض صفحه‌ای خالی است: مؤلفه‌های ارائه‌شده توسط Google فقط زمانی ظاهر می‌شوند که نماینده شما صریحاً آن‌ها را به سلسله‌مراتب نما اضافه کند و آن‌ها را جای‌گذاری کند.
  • از میاناهای برنامه‌سازی کاربردی قدیمی پشتیبانی نمی‌شود: از ویژگی‌ها و روش‌هایی که مدل چیدمان قدیمی مبتنی بر جایگاه را کنترل می‌کنند پشتیبانی نخواهد شد و ممکن است هنگام فعال بودن نماینده سفارشی، طبق انتظار رفتار نکنند.
  • ساختارهای نمای داخلی را تغییر ندهید: سلسله‌مراتب نمای مؤلفه‌های ارائه‌شده توسط Google (مانند کارت پیچ یا کارت زمان تخمینی رسیدن) را پیمایش یا تغییر ندهید. چون این سلسله‌مراتب‌های نمای داخلی جزئیات پیاده‌سازی زیربنایی هستند، ممکن است در نسخه‌های مختلف کیت توسعه نرم‌افزار تغییر کنند. اصلاح آن‌ها می‌تواند باعث شود چیدمان شما درطول به‌روزرسانی‌های آتی کیت توسعه نرم‌افزار خراب شود.

سازگاری با API قدیمی

برای رفتار چیدمان قابل‌اعتماد هنگام استفاده از نماینده چیدمان سفارشی، از استفاده از میاناهای برنامه‌سازی کاربردی قدیمی مبتنی بر جایگاه زیر که قرار است منسوخ شوند خودداری کنید. برای انتقال برنامه موجود، استفاده از این میاناهای برنامه‌سازی کاربردی را با کد در نماینده چیدمان سفارشی‌تان جایگزین کنید:

نمایش میاناهای برنامه‌سازی کاربردی قدیمی مبتنی بر جایگاه

Legacy API جایگزینی Layout Delegate
isNavigationHeaderEnabled نمای turnCard را در startGuidance(navMap:newState:oldState:) بخوانید.
isNavigationFooterEnabled نمای etaCard را در startGuidance(navMap:newState:oldState:) بخوانید.
isNavigationTripProgressBarEnabled نمای tripProgressBar را در startGuidance(navMap:newState:oldState:) بخوانید.
isNavigationReportIncidentButtonEnabled دکمه .reporting را در آرایه buttons پیدا کنید.
‫setHeaderAccessoryView(_:) و invalidateLayout(forAccessoryView:) نمای خودتان را به چیدمان اضافه کنید و آن را به turnCard لنگر کنید.
‫padding و paddingAdjustmentBehavior نمای viewport را طوری قرار دهید که دوربین را قاب‌بندی کند.
navigationHeaderLayoutGuide نماهای سفارشی‌تان را مستقیماً به turnCard سنجاق کنید.
navigationFooterLayoutGuide نماهای سفارشی‌تان را مستقیماً به etaCard سنجاق کنید.
bottomTrailingButtonsLayoutGuide دکمه‌هایتان را مستقیماً به etaCard یا راهنماهای محفظه متصل کنید.
bottomTrailingButtonsAlternateLayoutGuide دکمه‌هایتان را مستقیماً به دکمه .reporting یا محدودیت‌های wideMode متصل کنید.
‫navigatorWillPresentPrompt(_:) و navigatorDidDismissPrompt(_:) به فراخوانی‌های برگشتی پیام‌واره NavigationLayoutDelegate خود، مانند showPrompt(navMap:newPrompt:)، تکیه کنید.

وقتی نماینده چیدمان سفارشی تنظیم شده باشد، راهنماهای چیدمان قدیمی (مثل navigationHeaderLayoutGuide یا bottomTrailingButtonsLayoutGuide) و روش‌های نمای لوازم جانبی سرصفحه تأثیری ندارند. درعوض، نماهایتان را به نماهای ارائه‌شده توسط Google متصل کنید.

ویژگی compassButton از GMSUISettings همچنان قطب‌نما را کنترل می‌کند درحالی‌که واسط کاربری ناوبری غیرفعال است. وقتی ناوبری فعال است، نماینده شما تصمیم می‌گیرد که قطب‌نما را از آرایه buttons نشان دهد یا نه.

بازبینه موارد ضروری

برای پیاده‌سازی موفقیت‌آمیز نماینده چیدمان، این مراحل و الزامات ضروری را دنبال کنید:

  • قبل‌از فعال کردن پیمایش، نماینده را تنظیم کنید: قبل‌از فعال کردن پیمایش با isNavigationEnabled یا enableNavigation(with:)، setLayoutDelegate(_:) را در GMSMapView فراخوانی کنید. اگر ناوبری ازقبل فعال شده باشد یا اگر نمای نقشه را برای صفحه‌نمایش CarPlay ایجاد کرده باشید، این روش خطا ایجاد می‌کند. نمای نقشه ارجاع قوی به نماینده شما را حفظ می‌کند. برای بازیابی چیدمان داخلی، وقتی پیمایش غیرفعال است، nil را رد کنید.

    // Set the layout delegate before you enable navigation.
    do {
      try mapView.setLayoutDelegate(MyLayoutDelegate())
    } catch {
      print("Couldn't set the layout delegate: \(error)")
    }
    mapView.isNavigationEnabled = true
        
  • پیوست کردن نماهای اجباری: باید نشان‌واره Google (googleLogo) و «دریچه دید» (viewport) را در همه حالت‌های ناوبری به ظرف چیدمانتان اضافه کنید.

  • رعایت ابعاد ثابت: کیت توسعه نرم‌افزار ابعاد چندین مؤلفه ارائه‌شده توسط Google را تعیین می‌کند. محدودیت‌های عرض یا ارتفاع سفارشی را برای عناصر زیر اعمال نکنید:

    • کارت گردش
    • کارت زمان تقریبی ورود
    • پیام‌واره‌های ورودی
    • نشان‌واره Google
    • ابزاره سرعت
  • افزودن عناصر به‌عنوان زیرنما: نماهای ارائه‌شده توسط Google را مستقیماً به‌عنوان زیرنما به نمای محفظه سفارشی خود یا به خود GMSMapView اضافه کنید.

  • تراز کردن بااستفاده از راهنماهای چیدمان: نماهای ارائه‌شده توسط Google شامل حاشیه‌های دیداری داخلی است. برای تراز کردن کامل عناصر سفارشی با مؤلفه‌های ارائه‌شده توسط Google، نماهای خود را به ویژگی layoutMarginsGuide نمای Google محدود کنید.

  • پاک‌سازی هنگام خروج از وضعیت: در stopGuidance(navMap:newState:oldState:)، نماهای راهنمایی فعال را بردارید و محدودیت‌های آن‌ها را قبل‌از بازیابی چیدمان آماده ناوبری غیرفعال کنید. در disableNavigationUI(navMap:oldState:)، همه نماها و راهنماهای چیدمانی را که اضافه کرده‌اید بردارید، ازجمله هر پیام‌واره‌ای که روی صفحه است. این کار از هم‌پوشانی نماهای یک حالت با حالت بعدی جلوگیری می‌کند.

  • پشتیبانی از دسترس‌پذیری و راست‌به‌چپ: نماهای ارائه‌شده توسط Google استانداردهای دسترس‌پذیری را برآورده می‌کنند، اما نماینده شما مسئول دسترس‌پذیری چیدمان کلی است. برای پشتیبانی از زبان‌های راست به چپ، به‌جای لنگرهای چپ و راست، از لنگرهای پیشرو و دنباله‌رو استفاده کنید.

  • از میاناهای برنامه‌سازی کاربردی چیدمان قدیمی استفاده نکنید: وقتی نماینده سفارشی فعال است، از ویژگی‌های اصلاح چیدمان قدیمی در GMSUISettings یا راهنماهای چیدمان قدیمی در GMSMapView استفاده نکنید. وقتی نماینده سفارشی پیوست شده باشد، کیت توسعه نرم‌افزار ممکن است این تماس‌های قدیمی را نادیده بگیرد.

وضعیت‌های واسط کاربر و مؤلفه‌های Google

وقتی انتقال وضعیت ناوبری رخ می‌دهد، کیت توسعه نرم‌افزار یک شیء فقط خواندنی NavigationUIState را به فراخوانی برگشتی نماینده شما ارسال می‌کند. این شیء پرچم‌های پیکربندی چیدمان فعلی را با مؤلفه‌های Google (برای مثال، نمونه‌های UIView) که برای رندر کردن صفحه نیاز دارید، بسته‌بندی می‌کند.

نماینده چیدمان سلسله‌مراتب نما را در چهار حالت عملیاتی مدیریت می‌کند. نمودار وضعیت زیر نشان می‌دهد که کیت توسعه نرم‌افزار چگونه بین وضعیت‌های ناوبری جابه‌جا می‌شود و کدام روش‌های فراخوانی را در نماینده شما اجرا می‌کند:

نمودار انتقال حالت چرخه حیات NavigationLayoutDelegate که انتقال بین حالت‌های «غیرناوبری»، «آماده ناوبری»، و «راهنمایی فعال» را نشان می‌دهد.

مؤلفه‌های خاص Google که به نماینده شما ارائه می‌شود به مرحله فعلی چرخه حیات پیمایش بستگی دارد.

عناصر الزامی (همه حالت‌های پیمایش)

صرف‌نظر از فاز ناوبری فعلی، باید مؤلفه‌های Google زیر را در سلسله‌مراتب نمای خود بگنجانید، جای‌گذاری کنید، و قابل‌مشاهده نگه دارید:

  • نشان‌واره Google (googleLogo): این مؤلفه نشان‌واره اجباری Google Maps را نمایش می‌دهد. اگر دکمه مرکز کردن مجدد فعال باشد، وقتی راننده نقشه را از خودرو خود دور می‌کند، نشان‌واره به‌طور خودکار به این دکمه تبدیل می‌شود. درنتیجه، محل قرار دادن نشان‌واره تعیین می‌کند دکمه مرکزیابی دوباره کجا نشان داده شود. توصیه می‌شود نشان‌واره را در گوشه پایین سمت راست چیدمان قرار دهید تا با انتظارات واسط کاربری استاندارد نقشه مطابقت داشته باشد.

  • ناحیه نمایش (viewport): UIView نامرئی که مرزهای قاب‌بندی دوربین را تعریف می‌کند. جایگاه نمای هدف که شما تعیین می‌کنید دقیقاً مشخص می‌کند که کیت توسعه نرم‌افزار کجا نشانگر خودرو را در مرکز قرار دهد و خط مسیر فعال را ترسیم کند. نمای هدف را طوری قرار دهید که ناحیه باز و بدون مانع صفحه‌نمایش را بپوشاند و به‌طور ایمن از هم‌پوشانی‌های مات مانند برگه‌های سفارشی پایین اجتناب کند.

درطول وضعیت آماده پیمایش، واسط کاربر همچنان ساده باقی می‌ماند تا تمرکز روی نقشه مبنا حفظ شود. شیء NavigationReadyUIState شما دسترسی به موارد زیر را فراهم می‌کند:

  • دکمه‌ها: آرایه‌ای از نماهای کنش شناور که برای نقشه مبنا پیکربندی شده‌اند (معمولاً فقط دکمه «قطب‌نما»). چون ممکن است Google در نسخه‌های آتی کیت توسعه نرم‌افزار دکمه‌هایی را اضافه کند یا ترتیب آن‌ها را تغییر دهد، از تکیه بر نمایه‌های آرایه ثابت خودداری کنید. به‌جای آن، در آرایه تکرار کنید، نوع هر دکمه را با مقایسه کردن ویژگی type آن با NavigationButtonKnownType.compass بخوانید، و نمای فیزیکی را با فراخوانی ویژگی view آن استخراج کنید. توجه داشته باشید که دکمه‌های دقیق در این آرایه با دکمه‌های دردسترس درطول راهنمایی فعال متفاوت است.

‫ مؤلفه راهنمایی فعال

وقتی راهنمایی گام‌به‌گام شروع می‌شود، کیت توسعه نرم‌افزار مجموعه کامل کنترل‌های ناوبری را باز می‌کند. شیء ActiveGuidanceUIState شما دسترسی به مؤلفه‌های Google زیر را فراهم می‌کند:

  • turnCard: برنمای سرصفحه اصلی که جهت‌های مانور پیش‌رو، اندازه‌گیری‌های فاصله، و راهنمای لاین را نمایش می‌دهد. آن را در بالای چیدمان قرار دهید تا سلسله‌مراتب پیمایش آشنایی ایجاد کنید و نماهای سفارشی‌تان را در اطراف آن لنگر کنید.

  • etaCard: برنمای پانویس که زمان رسیدن تخمینی، زمان باقی‌مانده سفر، و فاصله باقی‌مانده تا مقصد را نمایش می‌دهد. آن را در لبه پایینی صفحه‌نمایش قرار دهید یا مختصات آن را با برگه‌های مدیریت تکلیف سفارشی‌تان ادغام کنید.

  • tripProgressBar: نوار پیشرفت عمودی که به راننده نشان می‌دهد چقدر در مسیر فعلی پیش رفته است. برخلاف چیدمان‌های قدیمی که این دکمه را به‌طور ثابت در لبه نقشه قرار می‌دادند، شما آزادی کامل دارید تا آن را در هر جایی لنگر کنید—برای مثال، در امتداد لبه کناری ظرف سفارشی‌تان.

  • speedWidget: کنترل شناور برای سرعت‌سنج و محدودیت‌های سرعت اعلام‌شده. بسته به تنظیمات میانای برنامه‌سازی کاربردی شما (ویژگی‌های shouldDisplaySpeedometer و shouldDisplaySpeedLimit از GMSMapView) و دردسترس بودن داده‌ها، این نما به‌طور پویا بین چهار حالت بصری در زمان اجرا تغییر اندازه می‌دهد (هیچ‌چیز نمایش داده نمی‌شود، فقط سرعت فعلی، فقط حد مجاز سرعت، یا هر دو خوانش). چون ابزاره می‌تواند بدون اطلاع قبلی بین این اندازه‌ها جابه‌جا شود، همیشه نماهای اطراف را بااستفاده از محدودیت‌ها لنگر کنید تا چیدمان شما به‌طور خودکار با شرایط سازگار شود و از هم‌پوشانی فضایی جلوگیری شود.

  • buttons: آرایه گسترده‌ای از نماهای کنش شناور برای حالت راهنمایی فعال (معمولاً شامل دکمه «قطب‌نما» و «گزارش رخداد»). درست مانند حالت آماده برای پیمایش، می‌توانید با فیلتر کردن آرایه براساس type (.compass یا .reporting) و استخراج نمای فیزیکی بااستفاده از ویژگی view آن، نماهای دکمه‌های تکی را پیدا و استخراج کنید. سپس می‌توانید آن‌ها را به‌طور مستقل قرار دهید یا به GMSNavigationHidingStackView اضافه کنید. این نمای پشته‌ای دکمه‌ها را به‌صورت عمودی مرتب می‌کند و وقتی فضا تمام می‌شود، آن‌ها را از انتهای آرایه arrangedSubviews خود پنهان می‌کند. isHidden را روی نماهایی که مدیریت می‌کند تنظیم نکنید.
نکته: روال مطلوب برای قاب‌بندی دوربین. نمای کارت پیچیدن ارتفاع ثابتی دارد که براساس NavigationStyleValues.headerNominalHeight است و برای هرگونه محتوای کارت پیچیدن به‌اندازه کافی بلند است. برای ثابت نگه داشتن قاب‌بندی دوربین، بالای viewport را مستقیماً به لبه پایینی turnCard محدود کنید، همان‌طور که در پیاده‌سازی نمونه نشان داده شده است.

مؤلفه‌های پیام‌واره پویا

پیام‌واره‌هایی مثل هشدارهای حادثه یا هشدارهای دوربین ایمنی به‌طور مستقل درطول راهنمایی فعال راه‌اندازی می‌شوند.

وقتی پیام‌واره‌ای آماده نمایش باشد، کیت توسعه نرم‌افزار (SDK) با showPrompt(navMap:newPrompt:) نماینده شما تماس می‌گیرد و مستقیماً نمای newPrompt را ارسال می‌کند. نماینده شما مسئول قرار دادن روان این پیام‌واره روی چیدمان شما است (معمولاً به لبه پایینی ظرف نقشه شما متصل می‌شود).

چون پیام‌واره ورودی بخش پایینی صفحه‌نمایش را می‌پوشاند، باید چیدمانتان را به‌روز کنید تا پیام‌واره با نمای دید، نشان‌واره Google، یا دکمه‌های تراز پایین هم‌پوشانی نداشته باشد.

نماینده شما همچنین نحوه ورود و خروج پیام‌واره‌ها از صفحه‌نمایش را کنترل می‌کند. برای جزئیات، به سفارشی‌سازی پیام‌واره‌ها مراجعه کنید.

مدیریت اندازه صفحه‌نمایش و حالت عریض

برای مدیریت تغییرات در ابعاد نقشه و جهت صفحه، نماینده چیدمان از ویژگی‌های زیر استفاده می‌کند:

  • تغییر اندازه نما: هرگاه اندازه فیزیکی نقشه تغییر کند، چیدمان را تنظیم می‌کند.

  • حالت عریض: وقتی نقشه به‌اندازه کافی عریض باشد، به نوع چیدمان با قالب عریض تغییر می‌کند.

پاسخ به تغییر اندازه نما

چیدمان‌های صفحهٔ دونیمه، لغزنده‌های محفظه، و چرخش دستگاه معمولاً فراخوان sizeChanged(navMap:newState:) را راه‌اندازی می‌کنند. از این فراخوان برای انجام تنظیمات واکنش‌گرای کلی در واسط کاربر سفارشی‌تان استفاده کنید. sizeChanged(navMap:newState:) را پیاده‌سازی کنید تا عناصر سفارشی را برای نسبت ابعادی جدید صفحه‌نمایش تغییر مکان دهید، نقاط شکست عرض یا ارتفاع سفارشی خودتان را اعمال کنید، و تشخیص دهید که چه زمانی رویداد تغییر اندازه حالت عریض را تغییر می‌دهد تا انواع چیدمان را به‌طور ایمن جابه‌جا کنید.

آشنایی با حالت عریض

حالت عریض زمانی فعال می‌شود که ظرف نقشه به‌اندازه کافی عریض باشد تا مؤلفه‌های میانای کاربر را در کنار هم نمایش دهد.

مقدار بولی wideMode را از شیء وضعیت بخوانید تا عناصر میانای کاربر سفارشی‌تان را جابه‌جا کنید و مرکز نقشه را برای راننده خالی نگه دارید. چون مؤلفه‌های ارائه‌شده توسط Google مانند کارت پیچ و کارت زمان تخمینی ورود به‌طور خودکار در حالت عریض کوچک می‌شوند و تغییر شکل می‌دهند، خواندن این مقدار بولی تضمین می‌کند که چیدمان شما دقیقاً در همان لحظه‌ای که مؤلفه‌های Google به‌روز می‌شوند، سازگار شود.

نمونه‌های زیر را برای قرار دادن مؤلفه‌های چیدمان در حالت‌های استاندارد و عریض درنظر بگیرید:

  • حالت چهره استاندارد: کارت پیچ را در بالای صفحه و کارت زمان تخمینی رسیدن را در پایین قرار دهید.

  • حالت عریض: کارت پیچ و کارت زمان تخمینی رسیدن را در امتداد لبه جلویی صفحه نگه دارید، به‌طوری‌که کارت پیچ در بالا و کارت زمان تخمینی رسیدن در پایین باشد. قرار دادن هریک از کارت‌ها دور از لبه جلویی منجر به رفتار نامشخص می‌شود. می‌توانید نمای دید و محتوای خودتان را در هر جایی قرار دهید؛ یک انتخاب رایج، سمت دنباله‌دار کارت‌ها است.

بررسی حالت عریض درطول انتقال وضعیت

اگر چیدمان شما از متغیر حالت عریض پشتیبانی می‌کند، به‌جای اینکه فقط به sizeChanged(navMap:newState:) تکیه کنید، wideMode بولی را در هر فراخوانی برگشتی انتقال حالت ارزیابی کنید.

شیء وضعیت را درطول بازخوانی‌هایی مثل enableNavigationUI(navMap:newState:) و startGuidance(navMap:newState:oldState:) بررسی کنید تا راه‌اندازی‌های اولیه برنامه را در جهت افقی مدیریت کنید. این رویکرد تضمین می‌کند که چیدمان شما وضعیت حالت عریض را ارزیابی می‌کند و به‌محض شروع وضعیت پیمایش جدید، نوع چیدمان استاندارد یا عریض صحیح را فعال می‌کند.

درحال تراز کردن با سبک‌پردازی Google

برای کمک به نماهای سفارشی شما در مطابقت با فاصله‌گذاری مؤلفه‌های ارائه‌شده توسط Google، کیت توسعه نرم‌افزار کلاس NavigationStyleValues را ارائه می‌دهد. ویژگی‌های کلاس آن اندازه‌گیری‌ها را به‌صورت نقطه برمی‌گرداند.

برای مثال، برای قرار دادن دکمه سفارشی در گوشه بالای صفحه مقابل کارت پیچ، از NavigationStyleValues.headerTopPadding به‌عنوان حاشیه بالای دکمه استفاده کنید. سپس دکمه با لبه بالایی کارت پیچیدن هم‌تراز می‌شود.

اندازه‌گیری‌های زیر دردسترس است:

  • NavigationStyleValues.buttonHorizontalPadding
  • NavigationStyleValues.buttonVerticalPadding
  • NavigationStyleValues.defaultCornerRadius
  • NavigationStyleValues.elementHorizontalPadding
  • NavigationStyleValues.headerFooterHorizontalPadding
  • NavigationStyleValues.headerNominalHeight
  • NavigationStyleValues.headerTopPadding

نماهای ارائه‌شده توسط Google شامل حاشیه‌های داخلی نیز می‌شود. وقتی دو نمای ارائه‌شده توسط Google را روی هم قرار می‌دهید، یکی را به layoutMarginsGuide دیگری محدود کنید تا حاشیه‌های آن‌ها باهم جمع نشود. برای مثال، برای قرار دادن ابزاره سرعت بالای کارت زمان تخمینی ورود، لنگر پایین speedWidget را به etaCard.layoutMarginsGuide.topAnchor محدود کنید.

سفارشی‌سازی کردن پیام‌واره‌ها

نماینده چیدمان شما نحوه ورود و خروج پیام‌واره‌ها به صفحه‌نمایش و اینکه کدام‌یک از گوشه‌های آن‌ها گرد باشد را کنترل می‌کند.

پیام‌واره‌های متحرک‌سازی

کیت توسعه نرم‌افزار پیام‌واره‌ها را برای شما پویانمایی نمی‌کند. نماینده شما تصمیم می‌گیرد که هر پیام‌واره چگونه ظاهر و ناپدید شود:

  • در showPrompt(navMap:newPrompt:)، پیام‌واره را در موقعیت شروع به نمای نقشه اضافه کنید و سپس آن را در جای خود پویانمایی کنید. برای مثال، پیام‌واره را درست زیر ناحیه قابل‌مشاهده یا پشت برگه پایینی شروع کنید، به‌طوری‌که لبه بالایی آن با بالای برگه تراز باشد.
  • در hidePrompt(navMap:oldPrompt:)، پیام‌واره را از دید پویانمایی کنید، و سپس وقتی پویانمایی تمام شد، آن را از نمای فوق‌العاده‌اش بردارید.
  • در changePrompt(navMap:newPrompt:oldPrompt:)، پویانمایی‌های پنهان کردن و نشان دادن را هم‌زمان اجرا کنید تا یک پیام‌واره با پیام‌واره دیگری جایگزین شود.

نمونه پیاده‌سازی از این رویکرد استفاده می‌کند.

برای خاموش کردن پیام‌واره‌های مربوط به ترافیک، مسیرهای بهتر، و حوادث، ویژگی shouldDisplayPrompts مربوط به GMSNavigator را روی false تنظیم کنید.

گرد کردن گوشه‌های پیام‌واره

پیام‌واره‌ها با پروتکل NavigationRoundedCornerView مطابقت دارند، بنابراین می‌توانید انتخاب کنید کدام‌یک از گوشه‌های آن‌ها گرد باشد. به‌طور پیش‌فرض، پیام‌واره فقط گوشه‌های بالایی خود را گرد می‌کند که برای پیام‌واره‌ای که به لبه پایینی صفحه متصل شده است مناسب است. اگر پیام‌واره‌ای را بالای محتوای دیگری، مثل برگه پایین، قرار می‌دهید، هر چهار گوشه را گرد کنید:

newPrompt.roundedCorners = [
  .layerMinXMinYCorner, .layerMaxXMinYCorner,
  .layerMinXMaxYCorner, .layerMaxXMaxYCorner,
]

مثال: پیاده‌سازی چیدمان مبتنی بر محدودیت

مثال زیر یک نماینده چیدمان پایه را نشان می‌دهد که از محدودیت‌های «چیدمان خودکار» برای قرار دادن کارت پیچ، کارت زمان تخمینی ورود، دکمه‌ها، نشان‌واره Google، و نمای هدف استفاده می‌کند. همچنین پیام‌واره‌ها را به داخل و خارج صفحه می‌لغزاند. برای اینکه مثال کوتاه باشد، حالت عریض، ابزارک سرعت، یا نوار پیشرفت سفر را مدیریت نمی‌کند، بنابراین برای برنامه‌هایی مناسب است که فقط از جهت عمودی پشتیبانی می‌کنند.

import GoogleNavigation
import UIKit

/// A layout delegate that positions the essential navigation UI elements: the
/// turn card, ETA card, buttons, Google logo, and viewport. It also slides
/// prompts on and off the screen.
@MainActor
final class BasicLayoutDelegate: NSObject, NavigationLayoutDelegate {
  /// How long prompts take to slide on or off the screen, in seconds.
  private static let promptAnimationDuration: TimeInterval = 0.4

  /// The views and constraints for one navigation state.
  @MainActor
  private final class StateElements {
    let buttonStack = GMSNavigationHidingStackView(
      position: .bottom, alignment: .trailing)
    var constraints: [NSLayoutConstraint] = []
    private unowned let navMap: GMSMapView
    private let buttons: [UIView]
    private let topLevelViews: [UIView]

    init(
      navMap: GMSMapView, buttons: [NavigationUIButton],
      topLevelViews: [UIView]
    ) {
      self.navMap = navMap
      self.buttons = buttons.map { $0.view }
      self.topLevelViews = topLevelViews
    }

    /// Adds the views to the map view, and then activates the constraints.
    func activate() {
      topLevelViews.forEach { navMap.addSubview($0) }
      buttonStack.arrangedSubviews = buttons
      navMap.addSubview(buttonStack)
      NSLayoutConstraint.activate(constraints)
    }

    /// Deactivates the constraints, and then removes the views.
    func deactivate() {
      NSLayoutConstraint.deactivate(constraints)
      buttonStack.removeFromSuperview()
      buttonStack.arrangedSubviews = []
      topLevelViews.forEach { $0.removeFromSuperview() }
    }
  }

  private var navigationReadyElements: StateElements?
  private var activeGuidanceElements: StateElements?
  private var currentPrompt: NavigationPromptView?
  private var promptBottomConstraint: NSLayoutConstraint?

  /// Constraints that apply in both the navigation-ready and active guidance
  /// states.
  private var sharedConstraints: [NSLayoutConstraint] = []

  /// Marks the bottom of the map content area. The guide stays above the ETA
  /// card and above any prompt that's on the screen.
  private let mapContentsBottomGuide = UILayoutGuide()

  // MARK: - Navigation-ready state

  func enableNavigationUI(
    navMap: GMSMapView, newState: NavigationReadyUIState
  ) {
    let safeArea = navMap.safeAreaLayoutGuide
    let bottomGuide = mapContentsBottomGuide
    navMap.addLayoutGuide(bottomGuide)

    // Keep the guide at the bottom of the safe area unless a higher-priority
    // constraint pushes it up.
    let lowPriorityBottom = bottomGuide.bottomAnchor.constraint(
      equalTo: safeArea.bottomAnchor)
    lowPriorityBottom.priority = .defaultLow
    sharedConstraints = [
      bottomGuide.heightAnchor.constraint(equalToConstant: 0),
      lowPriorityBottom,
    ]

    let logo = newState.googleLogo
    let viewport = newState.viewport
    let elements = StateElements(
      navMap: navMap, buttons: newState.buttons,
      topLevelViews: [logo, viewport])
    let buttonStack = elements.buttonStack
    elements.constraints = sharedConstraints + [
      buttonStack.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
      buttonStack.topAnchor.constraint(equalTo: safeArea.topAnchor),
      buttonStack.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),

      logo.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
      logo.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),

      viewport.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
      viewport.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
      viewport.topAnchor.constraint(equalTo: safeArea.topAnchor),
      viewport.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
    ]
    elements.activate()
    navigationReadyElements = elements
  }

  func disableNavigationUI(
    navMap: GMSMapView, oldState: NavigationReadyUIState
  ) {
    currentPrompt?.removeFromSuperview()
    currentPrompt = nil
    promptBottomConstraint = nil
    navigationReadyElements?.deactivate()
    navigationReadyElements = nil
    navMap.removeLayoutGuide(mapContentsBottomGuide)
  }

  // MARK: - Active guidance state

  func startGuidance(
    navMap: GMSMapView, newState: ActiveGuidanceUIState,
    oldState: NavigationReadyUIState
  ) {
    navigationReadyElements?.deactivate()

    let safeArea = navMap.safeAreaLayoutGuide
    let bottomGuide = mapContentsBottomGuide
    let turnCard = newState.turnCard
    let etaCard = newState.etaCard
    let logo = newState.googleLogo
    let viewport = newState.viewport
    let elements = StateElements(
      navMap: navMap, buttons: newState.buttons,
      topLevelViews: [turnCard, etaCard, viewport, logo])
    let buttonStack = elements.buttonStack
    elements.constraints = sharedConstraints + [
      turnCard.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
      turnCard.topAnchor.constraint(equalTo: navMap.topAnchor),

      etaCard.leadingAnchor.constraint(equalTo: navMap.leadingAnchor),
      etaCard.bottomAnchor.constraint(equalTo: navMap.bottomAnchor),
      bottomGuide.bottomAnchor.constraint(
        lessThanOrEqualTo: etaCard.topAnchor),

      buttonStack.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
      buttonStack.topAnchor.constraint(equalTo: turnCard.bottomAnchor),
      buttonStack.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),

      logo.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
      logo.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),

      // Frame the camera in the open area below the turn card.
      viewport.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
      viewport.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
      viewport.topAnchor.constraint(equalTo: turnCard.bottomAnchor),
      viewport.bottomAnchor.constraint(equalTo: bottomGuide.topAnchor),
    ]
    elements.activate()
    activeGuidanceElements = elements
  }

  func stopGuidance(
    navMap: GMSMapView, newState: NavigationReadyUIState,
    oldState: ActiveGuidanceUIState
  ) {
    activeGuidanceElements?.deactivate()
    activeGuidanceElements = nil
    navigationReadyElements?.activate()
  }

  // MARK: - Prompts

  func showPrompt(navMap: GMSMapView, newPrompt: NavigationPromptView) {
    navMap.addSubview(newPrompt)
    currentPrompt = newPrompt

    // Start with the prompt just below the bottom edge of the map view.
    let bottomConstraint = newPrompt.bottomAnchor.constraint(
      equalTo: navMap.bottomAnchor, constant: newPrompt.bounds.height)
    promptBottomConstraint = bottomConstraint
    NSLayoutConstraint.activate([
      bottomConstraint,
      newPrompt.leadingAnchor.constraint(
        equalTo: navMap.safeAreaLayoutGuide.leadingAnchor),
      // Keep the map content above the prompt.
      mapContentsBottomGuide.bottomAnchor.constraint(
        lessThanOrEqualTo: newPrompt.topAnchor),
    ])

    // Lay out the prompt in its starting position, and then slide it up.
    navMap.layoutIfNeeded()
    UIView.animate(withDuration: Self.promptAnimationDuration) {
      bottomConstraint.constant = 0
      navMap.layoutIfNeeded()
    }
  }

  func hidePrompt(navMap: GMSMapView, oldPrompt: NavigationPromptView) {
    guard let bottomConstraint = promptBottomConstraint else { return }
    promptBottomConstraint = nil
    currentPrompt = nil

    // Slide the prompt down, and then remove it.
    UIView.animate(withDuration: Self.promptAnimationDuration) {
      bottomConstraint.constant = oldPrompt.bounds.height
      navMap.layoutIfNeeded()
    } completion: { _ in
      oldPrompt.removeFromSuperview()
    }
  }

  func changePrompt(
    navMap: GMSMapView, newPrompt: NavigationPromptView,
    oldPrompt: NavigationPromptView
  ) {
    // Slide the old prompt out and the new prompt in at the same time.
    hidePrompt(navMap: navMap, oldPrompt: oldPrompt)
    showPrompt(navMap: navMap, newPrompt: newPrompt)
  }
}

برای استفاده از نماینده، آن را قبل‌از فعال کردن ناوبری در نمای نقشه تنظیم کنید، همان‌طور که در بازبینه موارد ضروری شرح داده شده است.

محدودیت‌ها و مشکلات شناخته‌شده

سفارشی‌سازی چیدمان برای CarPlay که از الگوهای چیدمان ثابت استفاده می‌کند اعمال نمی‌شود. اگر setLayoutDelegate(_:) را در نمای نقشه‌ای که برای صفحه‌نمایش CarPlay ایجاد می‌کنید فراخوانی کنید، این روش خطا ایجاد می‌کند.

برای مشکلاتی که نسخه‌های خاصی از کیت توسعه نرم‌افزار را تحت‌تأثیر قرار می‌دهند، به مشکلات شناخته‌شده در یادداشت‌های انتشار مراجعه کنید.