নেভিগেশন লেআউট কাস্টমাইজ করুন

iOS-এ কাস্টমাইজ করা নেভিগেশন লেআউট দেখানো অ্যানিমেশনের উদাহরণ

মনে রাখবেন: এই ডকুমেন্ট ক্লাস, প্রোটোকল ও API-এর জন্য Swift-এর নামকরণ পদ্ধতি ব্যবহার করে।

Google-এর প্রদান করা ভিউ এবং আপনার নিজের কাস্টম বিজনেস ভিউ, দুটির ক্ষেত্রেই ভিউ হায়ারার্কি এবং স্ক্রিন প্লেসমেন্ট নিয়ন্ত্রণ করতে লেআউট কাস্টমাইজেশন ব্যবহার করুন।

আগে থেকে নির্ধারিত লেআউট স্লটে কাস্টম ভিউ যোগ করার পরিবর্তে, সম্পূর্ণ স্ক্রিনের লেআউট ম্যানেজ করার জন্য লেআউট ডেলিগেট প্রয়োগ করুন। নেভিগেশন চলাকালীন স্টেট ট্রানজিশন, আপনার লেআউট ডেলিগেট Google-এর প্রদান করা কম্পোনেন্ট পায়, যেমন টার্ন কার্ড, ETA কার্ড এবং বোতাম। স্ট্যান্ডার্ড iOS অটো লেআউট কনস্ট্রেন্ট ব্যবহার করে আপনার নিজস্ব কাস্টম কন্টেন্টের পাশাপাশি এইসব এলিমেন্ট পজিশন করুন।

কাস্টম ব্যবসার তথ্য—যেমন অর্ডারের স্ট্যাটাস বা পিক-আপ সংক্রান্ত নির্দেশাবলী—স্ক্রিনে সঠিকভাবে দেখানোর জন্য এই ফ্রেমওয়ার্ক ব্যবহার করুন, যাতে আপনার ভিউ ওভারল্যাপ না করে।

লেআউট কাস্টমাইজেশন কীভাবে কাজ করে

লেআউট কাস্টমাইজেশন প্রতিনিধিত্বের নির্বাচন ডিজাইন প্যাটার্ন ব্যবহার করে। স্ক্রিনে UI কম্পোনেন্ট অটোমেটিক আঁকা বা পজিশন করার পরিবর্তে, SDK সরাসরি সেগুলিকে আপনার লেখা একটি কাস্টম ক্লাসে পাস করে: লেআউট ডেলিগেট।

আপনার লেআউট কাস্টমাইজ করতে, এমন একটি ক্লাস তৈরি করুন যা NavigationLayoutDelegate প্রোটোকল মেনে চলে এবং আপনার GMSMapView-এর setLayoutDelegate(_:) পদ্ধতিতে একটি ইনস্ট্যান্স পাস করুন। যখনই নেভিগেশন স্টেট ট্রানজিশন হয়—যেমন বেস ম্যাপ থেকে অ্যাক্টিভ এক এক করে নির্দেশনায় যাওয়া—SDK আপনার ডেলিগেটে কলব্যাক পদ্ধতি এক্সিকিউট করে এবং সেই নির্দিষ্ট স্টেটের জন্য উপলভ্য UI কম্পোনেন্ট প্রদান করে।

ব্যবহারকারীকে বাধাহীন, ইন্টিগ্রেট করা অভিজ্ঞতা প্রদান করতে, এই ফ্রেমওয়ার্ক আপনার অ্যাপ ও SDK-এর মধ্যে দায়িত্বের স্পষ্ট বিভাজন তৈরি করে। নিম্নলিখিত বিষয়গুলি কাস্টমাইজ করতে লেআউট ডেলিগেট ব্যবহার করুন:

  • ভিউ হায়ারার্কি তৈরি করুন: প্রতিটি UI স্ট্যাটাসের জন্য স্ক্রিনে কোন Google কম্পোনেন্ট এবং কাস্টম বিজনেস ভিউ যোগ করতে হবে তা সঠিকভাবে বেছে নিন।
  • প্রতিটি এলিমেন্টের পজিশন ঠিক করা: স্ক্রিন অ্যাঙ্কর, মার্জিন ও লেআউট প্লেসমেন্ট সঠিকভাবে সেট করুন। Google কম্পোনেন্টে কাস্টম প্রস্থ বা উচ্চতা সংক্রান্ত সীমাবদ্ধতা প্রয়োগ করা এড়িয়ে চলুন, কারণ Google কম্পোনেন্ট নিজের অভ্যন্তরীণ ডাইমেনশন নিজেই হিসেব করে।
  • বেস ম্যাপ ফ্রেম করুন: ভিউপোর্ট কম্পোনেন্টের কোঅর্ডিনেট ব্যবহার করে ম্যাপ ক্যামেরার দৃশ্যমান সীমানা নির্ধারণ করুন।
  • স্ক্রিন লেয়ার করা: কাস্টম ভিউ Google-এর বিল্ট-ইন কন্ট্রোলের উপরে, নিচে নাকি পাশে ভাসবে তা ঠিক করুন।

ইতিমধ্যে, আপনি লেআউট ডেলিগেটের সাথে নিম্নলিখিত Google কম্পোনেন্ট কাস্টমাইজ করতে পারবেন না:

  • কম্পোনেন্টের ডাইমেনশন: Google-এর প্রদান করা কম্পোনেন্টের সাইজ ও ইন্টার্নাল ডাইমেনশন, যা SDK অটোমেটিক গণনা করে।
  • ট্রিগার করার শর্ত: রিয়েল-টাইম রুট ডেটার উপর ভিত্তি করে ডায়নামিক অ্যালার্ট বা প্রম্পট দেখানো হলে।

প্রয়োগ সংক্রান্ত নীতি

লেআউট ডেলিগেট লেখার সময়, লেআউট সংক্রান্ত সমস্যা বা রানটাইম ক্র্যাশ এড়াতে নিম্নলিখিত নিয়মগুলি মনে রাখবেন:

  • ডিফল্ট হিসেবে স্ক্রিন খালি থাকে: Google-এর প্রদান করা কম্পোনেন্ট তখনই দেখা যায় যদি আপনার ডেলিগেট স্পষ্টভাবে ভিউ হায়ারার্কিতে সেগুলি যোগ করে এবং পজিশন করে।
  • পুরনো API কাজ করে না: পুরনো স্লট-ভিত্তিক লেআউট মডেল নিয়ন্ত্রণ করে এমন প্রপার্টি ও পদ্ধতি কাজ করবে না এবং কাস্টম ডেলিগেট অ্যাক্টিভ থাকলে প্রত্যাশা অনুযায়ী কাজ নাও করতে পারে।
  • ইন্টার্নাল ভিউ স্ট্রাকচার পরিবর্তন করবেন না: Google-এর প্রদান করা কম্পোনেন্টের (যেমন, টার্ন কার্ড বা ETA কার্ড) ভিউ হায়ারার্কি ট্রাভার্স বা পরিবর্তন করবেন না। যেহেতু এইসব ইন্টার্নাল ভিউ হায়ারার্কি হল অন্তর্নিহিত প্রয়োগের বিবরণ, তাই SDK রিলিজ জুড়ে এগুলি পরিবর্তন হতে পারে। এগুলি পরিবর্তন করলে, ভবিষ্যতে SDK আপডেট করার সময় আপনার লেআউট ভেঙে যেতে পারে।

পুরনো API-এর সাথে মানানসই হওয়া

কাস্টম লেআউট ডেলিগেট ব্যবহার করার সময় নির্ভরযোগ্য লেআউট আচরণ পেতে, নিম্নলিখিত লিগ্যাসি স্লট-ভিত্তিক API ব্যবহার করা এড়িয়ে চলুন, যেগুলি বন্ধ করার পরিকল্পনা করা হয়েছে। আগে থেকে আছে এমন অ্যাপ মাইগ্রেট করতে, আপনার কাস্টম লেআউট ডেলিগেটে কোড সহ এইসব API-এর ব্যবহার পরিবর্তন করুন:

পুরনো স্লট-ভিত্তিক API দেখুন

পুরনো API লেআউট ডেলিগেট রিপ্লেসমেন্ট
isNavigationHeaderEnabled startGuidance(navMap:newState:oldState:)-এ turnCard ভিউ পড়ুন।
isNavigationFooterEnabled startGuidance(navMap:newState:oldState:)-এ etaCard ভিউ পড়ুন।
isNavigationTripProgressBarEnabled startGuidance(navMap:newState:oldState:)-এ tripProgressBar ভিউ পড়ুন।
isNavigationReportIncidentButtonEnabled buttons অ্যারেতে .reporting বোতামটি খুঁজুন।
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-এর দেওয়া ভিউতে আপনার ভিউ অ্যাঙ্কর করুন।

নেভিগেশন UI বন্ধ থাকলেও GMSUISettings-এর compassButton প্রপার্টি এখনও কম্পাস নিয়ন্ত্রণ করে। নেভিগেশন চালু থাকলে, buttons অ্যারে থেকে কম্পাস দেখানো হবে কিনা তা আপনার ডেলিগেট ঠিক করে।

প্রয়োজনীয় চেকলিস্ট

আপনার লেআউট ডেলিগেট সফলভাবে প্রয়োগ করতে এইসব প্রয়োজনীয় ধাপ ও প্রয়োজনীয়তা অনুসরণ করুন:

  • নেভিগেশন চালু করার আগে ডেলিগেট সেট করুন: isNavigationEnabled বা enableNavigation(with:)-এর মাধ্যমে নেভিগেশন চালু করার আগে আপনার GMSMapView-এ setLayoutDelegate(_:) কল করুন। নেভিগেশন আগে থেকেই চালু থাকলে অথবা আপনি 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-এর প্রদান করা বিভিন্ন কম্পোনেন্টের ডাইমেনশন SDK নির্ধারণ করে। নিম্নলিখিত এলিমেন্টে কাস্টম প্রস্থ বা উচ্চতার সীমাবদ্ধতা প্রয়োগ করবেন না:

    • টার্ন কার্ড
    • ETA কার্ড
    • ইনকামিং প্রম্পট
    • Google লোগো
    • স্পিড উইজেট
  • সাবভিউ হিসেবে এলিমেন্ট যোগ করা: Google-এর প্রদান করা ভিউ সরাসরি আপনার কাস্টম কন্টেনার ভিউতে বা GMSMapView-এ সাবভিউ হিসেবে যোগ করুন।

  • লেআউট গাইড ব্যবহার করে অ্যালাইন করুন: Google-এর প্রদান করা ভিউতে বিল্ট-ইন ভিজ্যুয়াল মার্জিন অন্তর্ভুক্ত থাকে। Google-এর প্রদান করা কম্পোনেন্টের সাথে আপনার কাস্টম এলিমেন্টকে নিখুঁতভাবে সারিবদ্ধ করতে, Google ভিউয়ের layoutMarginsGuide প্রপার্টিতে আপনার ভিউকে সীমাবদ্ধ করুন।

  • স্টেট এক্সিটে ক্লিন-আপ: stopGuidance(navMap:newState:oldState:)-এ, আপনার নেভিগেশন-রেডি লেআউট রিস্টোর করার আগে অ্যাক্টিভ গাইডেন্স ভিউ সরিয়ে দিন এবং সেগুলির কনস্ট্রেন্ট ডিঅ্যাক্টিভেট করুন। disableNavigationUI(navMap:oldState:)-এ, স্ক্রিনে দেখানো যেকোনও প্রম্পট সহ আপনার যোগ করা সব ভিউ ও লেআউট গাইড সরিয়ে দিন। এটি একটি রাজ্যের ভিউকে পরবর্তী রাজ্যের ভিউয়ের সাথে ওভারল্যাপ করা থেকে আটকায়।

  • নিজস্ব অ্যাক্সেসিবিলিটি এবং ডান-থেকে-বাম সাপোর্ট: Google-এর প্রদান করা ভিউ অ্যাক্সেসিবিলিটি স্ট্যান্ডার্ড পূরণ করে, তবে আপনার ডেলিগেট সামগ্রিক লেআউটের অ্যাক্সেসিবিলিটির জন্য দায়ী। ডান-থেকে-বাঁদিকে লেখা হয় এমন ভাষা সাপোর্ট করতে, বাঁদিক ও ডানদিকের অ্যাঙ্করের পরিবর্তে লিডিং ও ট্রেলিং অ্যাঙ্কর ব্যবহার করুন।

  • লেগেসি লেআউট API এড়িয়ে চলুন: কাস্টম ডেলিগেট অ্যাক্টিভ থাকাকালীন GMSUISettings-এ লেগেসি লেআউট-মডিফাইং প্রপার্টি বা GMSMapView-এ লেগেসি লেআউট গাইড ব্যবহার করবেন না। কাস্টম ডেলিগেট অ্যাটাচ করা থাকলে, SDK এইসব লিগ্যাসি কল উপেক্ষা করতে পারে।

UI স্ট্যাটাস ও Google কম্পোনেন্ট

নেভিগেশন স্টেট ট্রানজিশন হলে, SDK আপনার ডেলিগেট কলব্যাকে একটি শুধু-পড়ার-উপযোগী NavigationUIState অবজেক্ট পাস করে। এই অবজেক্ট বর্তমান লেআউট কনফিগারেশন ফ্ল্যাগকে Google কম্পোনেন্টের (যেমন, UIView ইনস্ট্যান্স) সাথে বান্ডেল করে, যা স্ক্রিন রেন্ডার করার জন্য আপনার প্রয়োজন।

লেআউট ডেলিগেট চারটি অপারেশনাল স্টেট জুড়ে ভিউ হায়ারার্কি ম্যানেজ করে। নিচের স্টেট ডায়াগ্রাম থেকে বোঝা যায় যে কীভাবে SDK নেভিগেশন স্টেটের মধ্যে ট্রানজিশন করে এবং আপনার ডেলিগেটে কোন কলব্যাক পদ্ধতি প্রয়োগ করে:

NavigationLayoutDelegate লাইফসাইকেল স্টেট ট্রানজিশন ডায়াগ্রাম, যা নন-নেভিগেশন, নেভিগেশন রেডি ও অ্যাক্টিভ গাইডেন্স স্টেটের মধ্যে ট্রানজিশন দেখায়।

আপনার ডেলিগেটকে প্রদান করা নির্দিষ্ট Google কম্পোনেন্ট নেভিগেশন লাইফসাইকেলের বর্তমান পর্যায়ের উপর নির্ভর করে।

প্রয়োজনীয় কম্পোনেন্ট (সব নেভিগেশন স্ট্যাটাস)

বর্তমান নেভিগেশন ফেজ যাই হোক না কেন, আপনাকে অবশ্যই নিম্নলিখিত Google কম্পোনেন্টগুলি আপনার ভিউ হায়ারার্কিতে অন্তর্ভুক্ত করতে, পজিশন করতে এবং দৃশ্যমান রাখতে হবে:

  • Google লোগো (googleLogo): এই কম্পোনেন্ট বাধ্যতামূলক Google Maps লোগো দেখায়। আবার কেন্দ্রে নিয়ে আসুন বোতাম চালু করা থাকলে, ড্রাইভার তার গাড়ি থেকে দূরে ম্যাপ স্ক্রল করলে, লোগোটি অটোমেটিক এই বোতামে রূপান্তরিত হয়। এর ফলে, আপনি লোগোটি যেখানে প্লেস করবেন, সেখানে আবার ফোকাস করুন বোতামটি দেখা যাবে। স্ট্যান্ডার্ড ম্যাপ UI-এর প্রত্যাশার সাথে ম্যাচ করতে আপনার লেআউটের নিচের-লিডিং কোণে লোগোটি রাখার সাজেশন দেওয়া হয়।

  • ভিউপোর্ট (viewport): একটি অদৃশ্য UIView যা ক্যামেরার ফ্রেমিং বাউন্ডারি নির্ধারণ করে। আপনার ভিউপোর্ট প্লেসমেন্ট ঠিক করে দেয় যে SDK ঠিক কোথায় গাড়ির শেভরন সেন্টার করবে এবং অ্যাক্টিভ রুট লাইন আঁকবে। ভিউ পোর্ট এমনভাবে পজিশন করুন যাতে এটি আপনার স্ক্রিনের খোলা, অস্পষ্ট এলাকা কভার করে, কাস্টম বটম শিটের মতো অস্বচ্ছ ওভারলে নিরাপদে এড়িয়ে যায়।

নেভিগেশন-রেডি অবস্থায়, বেস ম্যাপে ফোকাস রাখার জন্য UI-তে ন্যূনতম তথ্য দেখানো হয়। আপনার NavigationReadyUIState অবজেক্ট নিম্নলিখিত অ্যাক্সেস প্রদান করে:

  • buttons: বেস ম্যাপের জন্য কনফিগার করা ফ্লোটিং অ্যাকশন ভিউয়ের একটি অ্যারে (সাধারণত শুধু কম্পাস বোতাম)। ভবিষ্যতের SDK রিলিজে Google বোতাম যোগ করতে বা সেগুলির ক্রম পরিবর্তন করতে পারে, তাই নির্দিষ্ট অ্যারে ইনডেক্সের উপর নির্ভর করবেন না। পরিবর্তে, অ্যারের মাধ্যমে ইটারেট করুন, প্রতিটি বোতামের ধরন পড়ুন, type প্রপার্টি NavigationButtonKnownType.compass-এর সাথে তুলনা করে এবং view প্রপার্টি কল করে ফিজিক্যাল ভিউ এক্সট্র্যাক্ট করুন। মনে রাখবেন যে এই অ্যারেতে থাকা সঠিক বোতামগুলি অ্যাক্টিভ গাইডেন্সের সময় উপলভ্য বোতামগুলির থেকে আলাদা।

টি অ্যাক্টিভ নির্দেশিকা কম্পোনেন্ট

এক এক করে নির্দেশিকা শুরু হলে, SDK নেভিগেশন কন্ট্রোলের সম্পূর্ণ সুইট আনলক করে। আপনার ActiveGuidanceUIState অবজেক্ট নিম্নলিখিত Google কম্পোনেন্টে অ্যাক্সেস প্রদান করে:

  • turnCard: প্রাইমারি হেডার ব্যানার যা আসন্ন ম্যানুভারের দিকনির্দেশ, দূরত্বের পরিমাপ এবং লেন সংক্রান্ত নির্দেশিকা দেখায়। পরিচিত নেভিগেশন হায়ারার্কি প্রতিষ্ঠা করতে এবং এর চারপাশে আপনার কাস্টম ভিউ অ্যাঙ্কর করতে এটি আপনার লেআউটের একেবারে উপরে রাখুন।

  • etaCard: ফুটার ব্যানারে আনুমানিক পৌঁছানোর সময়, বাকি থাকা যাত্রার সময় এবং গন্তব্যের বাকি থাকা দূরত্ব দেখানো হয়। এটি আপনার স্ক্রিনের নিচের প্রান্তে রাখুন অথবা আপনার কাস্টম টাস্ক-ম্যানেজমেন্ট শিটের সাথে এর কোঅর্ডিনেট ইন্টিগ্রেট করুন।

  • tripProgressBar: একটি উল্লম্ব প্রগ্রেস বার যা ড্রাইভারকে দেখায় বর্তমান রুটে তিনি কতদূর ভ্রমণ করেছেন। পুরনো লেআউটের মতো নয় যা ম্যাপের প্রান্তে এটিকে কঠোরভাবে স্থির করে, আপনার কাছে এটিকে যেকোনও জায়গায় অ্যাঙ্কর করার সম্পূর্ণ স্বাধীনতা রয়েছে—উদাহরণস্বরূপ, আপনার কাস্টম কন্টেনারের একটি পাশের প্রান্ত বরাবর।

  • speedWidget: স্পিডোমিটার ও পোস্ট করা স্পিড লিমিটের জন্য একটি ফ্লোটিং কন্ট্রোল। আপনার API সেটিংস (GMSMapView-এর shouldDisplaySpeedometer এবং shouldDisplaySpeedLimit প্রপার্টি) এবং ডেটার উপলভ্যতার উপর নির্ভর করে, এই ভিউ রানটাইমে চারটি ভিজ্যুয়াল স্টেটের মধ্যে ডায়নামিকভাবে ছোট বড় করা হয় (কিছুই না দেখানো, শুধুমাত্র বর্তমান গতি, শুধুমাত্র গতির সীমা, বা উভয় রিডিং)। যেহেতু উইজেটটি কোনও বিজ্ঞপ্তি ছাড়াই এইসব সাইজের মধ্যে পরিবর্তন করতে পারে, তাই সবসময় কনস্ট্রেন্ট ব্যবহার করে আশেপাশের ভিউ অ্যাঙ্কর করুন যাতে আপনার লেআউট অটোমেটিক স্পেশাল ওভারল্যাপ প্রতিরোধ করতে পারে।

  • buttons: অ্যাক্টিভ গাইডেন্স স্টেটের জন্য ফ্লোটিং অ্যাকশন ভিউয়ের একটি বর্ধিত অ্যারে (সাধারণত কম্পাস এবং ঘটনার রিপোর্ট করার বোতাম উভয়ই অন্তর্ভুক্ত থাকে)। নেভিগেশন-রেডি স্টেটের মতোই, আপনি type (.compass বা .reporting) দিয়ে অ্যারে ফিল্টার করে এবং view প্রপার্টি ব্যবহার করে ফিজিক্যাল ভিউ এক্সট্র্যাক্ট করে, স্বতন্ত্র বোতাম ভিউ লোকেট ও এক্সট্র্যাক্ট করতে পারবেন। আপনি তারপরে সেগুলিকে আলাদাভাবে পজিশন করতে পারবেন অথবা GMSNavigationHidingStackView-এ যোগ করতে পারবেন। এই স্ট্যাক ভিউ বোতামগুলিকে উল্লম্বভাবে সাজায় এবং, জায়গা শেষ হয়ে গেলে, এর arrangedSubviews অ্যারের শেষ থেকে শুরু করে সেগুলিকে লুকায়। এটি যে ভিউ ম্যানেজ করে তাতে isHidden সেট করবেন না।
পরামর্শ: ক্যামেরা ফ্রেমিংয়ের জন্য পেশাদার পদ্ধতি। টার্ন কার্ডের ভিউয়ের উচ্চতা NavigationStyleValues.headerNominalHeight-এর উপর ভিত্তি করে নির্দিষ্ট করা থাকে, যা যেকোনও টার্ন কার্ডের কন্টেন্টের জন্য যথেষ্ট লম্বা। ক্যামেরা ফ্রেমিং স্থির রাখতে, viewport-এর উপরের অংশ সরাসরি turnCard-এর নিচের প্রান্তে সীমাবদ্ধ করুন, যেমনটি উদাহরণস্বরূপ প্রয়োগ-এ দেখানো হয়েছে।

টি ডায়নামিক প্রম্পট কম্পোনেন্ট

অ্যাক্টিভ নির্দেশনার সময়, ঘটনা সংক্রান্ত সতর্কতা বা নিরাপত্তা ক্যামেরার সতর্কতার মতো প্রম্পট স্বতন্ত্রভাবে ট্রিগার হয়।

কোনও প্রম্পট দেখানোর জন্য রেডি হলে, SDK আপনার ডেলিগেটের showPrompt(navMap:newPrompt:) কলব্যাককে কল করে এবং সরাসরি একটি newPrompt ভিউ পাস করে। আপনার লেআউটের উপরে এই প্রম্পটটি মসৃণভাবে পজিশন করার জন্য আপনার ডেলিগেট দায়ী (সাধারণত আপনার ম্যাপ কন্টেনারের নিচের প্রান্তে অ্যাঙ্কর করা থাকে)।

ইনকামিং প্রম্পট স্ক্রিনের নিচের অংশ কভার করে, তাই আপনাকে লেআউট আপডেট করতে হবে যাতে প্রম্পট ভিউপোর্ট, Google লোগো বা নিচের দিকে অ্যালাইন করা কোনও বোতামের উপর ওভারল্যাপ না করে।

এছাড়াও, আপনার ডেলিগেট, প্রম্পট কীভাবে স্ক্রিনে প্রবেশ করে এবং স্ক্রিন থেকে বেরিয়ে যায় তা নিয়ন্ত্রণ করে। বিস্তারিত জানতে, প্রম্পট কাস্টমাইজ করা দেখুন।

স্ক্রিনের সাইজ ও ওয়াইড মোড ম্যানেজ করা

ম্যাপের ডাইমেনশন ও স্ক্রিন ওরিয়েন্টেশনে পরিবর্তন ম্যানেজ করতে, লেআউট ডেলিগেট নিম্নলিখিত ফিচার ব্যবহার করে:

  • ভিউ রিসাইজ করা: ম্যাপের ফিজিক্যাল সাইজ পরিবর্তন হলে আপনার লেআউট অ্যাডজাস্ট করে।

  • ওয়াইড মোড: ম্যাপ যথেষ্ট চওড়া হলে, ওয়াইড-ফর্ম্যাট লেআউট ভ্যারিয়েন্টে সোয়্যাপ করে।

ভিউ ছোট বড় করার জন্য রেসপন্ড করছে

স্প্লিট-স্ক্রিন লেআউট, কন্টেনার স্লাইডার এবং ডিভাইস রোটেশন সাধারণত sizeChanged(navMap:newState:) কলব্যাক ট্রিগার করে। আপনার কাস্টম UI-তে সাধারণ রেসপন্সিভ অ্যাডজাস্টমেন্ট করতে এই কলব্যাক ব্যবহার করুন। নতুন স্ক্রিন অ্যাস্পেক্ট রেশিওর জন্য কাস্টম এলিমেন্ট আবার পজিশন করতে, আপনার নিজস্ব কাস্টম প্রস্থ বা উচ্চতার ব্রেকপয়েন্ট প্রয়োগ করতে এবং রিসাইজ ইভেন্ট কখন ওয়াইড মোড টগল করে তা শনাক্ত করতে sizeChanged(navMap:newState:) প্রয়োগ করুন যাতে লেআউট ভেরিয়েন্ট নিরাপদে সোয়্যাপ করা যায়।

ওয়াইড মোড সম্পর্কে বোঝা

ম্যাপ কন্টেনার UI কম্পোনেন্ট পাশাপাশি দেখানোর জন্য যথেষ্ট চওড়া হলে, ওয়াইড মোড অ্যাক্টিভেট হয়।

আপনার কাস্টম UI এলিমেন্ট আবার পজিশন করতে এবং ড্রাইভারের জন্য ম্যাপের কেন্দ্রস্থল পরিষ্কার রাখতে, স্টেট অবজেক্ট থেকে wideMode বুলিয়ান পড়ুন। কারণ Google-এর প্রদান করা কম্পোনেন্ট যেমন টার্ন কার্ড এবং ETA কার্ড অটোমেটিক সঙ্কুচিত হয় এবং ওয়াইড মোডে রিশেপ হয়, এই বুলিয়ান রিডিং নিশ্চিত করে যে আপনার লেআউট Google কম্পোনেন্ট আপডেট হওয়ার ঠিক একই মুহূর্তে মানিয়ে নেয়।

স্ট্যান্ডার্ড ও ওয়াইড মোডে আপনার লেআউট কম্পোনেন্ট পজিশন করার জন্য নিম্নলিখিত উদাহরণগুলি বিবেচনা করুন:

  • স্ট্যান্ডার্ড পোর্ট্রেট মোড: স্ক্রিনের সবচেয়ে উপরে টার্ন কার্ড এবং সবচেয়ে নিচে ETA কার্ড রাখুন।

  • ওয়াইড মোড: স্ক্রিনের উপরের দিকে টার্ন কার্ড এবং নিচের দিকে ETA কার্ড রাখুন। লিডিং এজ থেকে দূরে কোনও কার্ড রাখলে অনির্ধারিত আচরণ দেখা যায়। আপনি ভিউপোর্ট এবং আপনার নিজস্ব কন্টেন্ট যেকোনও জায়গায় রাখতে পারেন; কার্ডের শেষ প্রান্তে রাখা একটি সাধারণ পছন্দ।

স্টেট ট্রানজিশনের সময় ওয়াইড মোড চেক করা হচ্ছে

আপনার লেআউট যদি ওয়াইড-মোড ভেরিয়েশন সাপোর্ট করে, তাহলে শুধু sizeChanged(navMap:newState:)-এর উপর নির্ভর না করে, প্রতিটি স্টেট ট্রানজিশন কলব্যাকের মধ্যে wideMode বুলিয়ান মূল্যায়ন করুন।

ল্যান্ডস্কেপ ওরিয়েন্টেশনে প্রাথমিক অ্যাপ লঞ্চ ম্যানেজ করতে enableNavigationUI(navMap:newState:) এবং startGuidance(navMap:newState:oldState:)-এর মতো কলব্যাকের সময় স্টেট অবজেক্ট চেক করুন। এই পদ্ধতি নিশ্চিত করে যে আপনার লেআউট ওয়াইড মোড স্টেট মূল্যায়ন করে এবং নতুন নেভিগেশন স্টেট শুরু হওয়ার সাথে সাথেই সঠিক স্ট্যান্ডার্ড বা ওয়াইড লেআউট ভেরিয়েন্ট অ্যাক্টিভেট করে।

Google স্টাইলিংয়ের সাথে অ্যালাইন করা হচ্ছে

Google-এর দেওয়া কম্পোনেন্টের স্পেসিংয়ের সাথে আপনার কাস্টম ভিউ ম্যাচ করাতে, SDK NavigationStyleValues ক্লাস প্রদান করে। এর ক্লাস প্রপার্টি পয়েন্টে পরিমাপ দেখায়।

যেমন, স্ক্রিনের উপরের কোণে টার্ন কার্ডের বিপরীতে কাস্টম বোতাম রাখতে, বোতামের উপরের মার্জিন হিসেবে NavigationStyleValues.headerTopPadding ব্যবহার করুন। তারপর বোতামটি টার্ন কার্ডের উপরের প্রান্তের সাথে সারিবদ্ধ হয়।

নিম্নলিখিত পরিমাপগুলি উপলভ্য:

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

Google-এর প্রদান করা ভিউতে বিল্ট-ইন মার্জিনও অন্তর্ভুক্ত থাকে। আপনি Google-এর প্রদান করা দুটি ভিউ স্ট্যাক করলে, একটিকে অন্যটির layoutMarginsGuide-এ সীমাবদ্ধ করুন যাতে তাদের মার্জিন যোগ না হয়। যেমন, ETA কার্ডের উপরে স্পিড উইজেট রাখতে, speedWidget-এর নিচের অ্যাঙ্কর etaCard.layoutMarginsGuide.topAnchor-এ সীমাবদ্ধ করুন।

প্রম্পট কাস্টমাইজ করা

আপনার লেআউট ডেলিগেট নিয়ন্ত্রণ করে যে কীভাবে প্রম্পট স্ক্রিনে প্রবেশ করে এবং স্ক্রিন থেকে বেরিয়ে যায় এবং সেগুলির কোন কোন কোণ গোলাকার।

অ্যানিমেটিং প্রম্পট

SDK আপনার জন্য প্রম্পট অ্যানিমেট করে না। আপনার ডেলিগেট প্রতিটি প্রম্পট কীভাবে দেখা যাবে এবং অদৃশ্য হবে তা নির্ধারণ করে:

  • showPrompt(navMap:newPrompt:)-এ, ম্যাপ ভিউতে প্রম্পটটি একটি শুরুর অবস্থানে যোগ করুন এবং তারপরে এটিকে জায়গায় অ্যানিমেট করুন। যেমন, দৃশ্যমান অংশের ঠিক নিচে অথবা স্ক্রিনের নিচে অ্যাটাচ করা শিটের পিছনে প্রম্পট শুরু করুন, যার উপরের প্রান্ত শিটের উপরের অংশের সাথে অ্যালাইন করা থাকে।
  • hidePrompt(navMap:oldPrompt:)-এ, ভিউ থেকে প্রম্পট অ্যানিমেট করুন এবং তারপরে অ্যানিমেশন শেষ হলে এর সুপারভিউ থেকে এটি সরান।
  • changePrompt(navMap:newPrompt:oldPrompt:)-এ, একটি প্রম্পটের জায়গায় অন্যটি দেখানোর জন্য, আপনার লুকানো ও দেখানোর অ্যানিমেশন একই সময়ে রান করুন।

প্রয়োগের উদাহরণ এই পদ্ধতি ব্যবহার করে।

ট্রাফিক, আরও ভালো রুট ও ঘটনার জন্য প্রম্পট বন্ধ করতে, GMSNavigator-এর shouldDisplayPrompts প্রপার্টি false-এ সেট করুন।

প্রম্পট কর্নার রাউন্ড করা হচ্ছে

প্রম্পট NavigationRoundedCornerView প্রোটোকল মেনে চলে, তাই আপনি বেছে নিতে পারেন কোন কোণগুলি গোলাকার হবে। ডিফল্ট হিসেবে, প্রম্পটের শুধুমাত্র উপরের কোণগুলি গোলাকার হয়, যা স্ক্রিনের নিচের প্রান্তে অ্যাঙ্কর করা প্রম্পটের জন্য উপযুক্ত। আপনি যদি অন্য কন্টেন্টের উপরে প্রম্পট রাখেন, যেমন স্ক্রিনের নিচে অ্যাটাচ করা শিট, তাহলে চারটি কোণই গোল করুন:

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

উদাহরণ: কনস্ট্রেন্ট-ভিত্তিক লেআউট প্রয়োগ করা

নিম্নলিখিত উদাহরণে একটি বেসিক লেআউট ডেলিগেট দেখানো হয়েছে যা অটো লেআউট কনস্ট্রেন্ট ব্যবহার করে টার্ন কার্ড, ETA কার্ড, বোতাম, 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-এর ক্ষেত্রে লেআউট কাস্টমাইজেশন প্রযোজ্য নয়, এটি নির্দিষ্ট লেআউট টেমপ্লেট ব্যবহার করে। CarPlay স্ক্রিনের জন্য আপনার তৈরি করা ম্যাপ ভিউতে setLayoutDelegate(_:) কল করলে, এই পদ্ধতিটি একটি সমস্যা দেখায়।

নির্দিষ্ট SDK ভার্সনকে প্রভাবিত করে এমন সমস্যার জন্য, রিলিজ নোটের পরিচিত সমস্যা দেখুন।